diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..2d3ebe8d --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,67 @@ +name: tests + +on: + push: + branches: [master, claude_code_dev] + pull_request: + branches: [master] + workflow_dispatch: + +jobs: + matlab-tests: + name: MATLAB ${{ matrix.matlab-release }} on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest] + matlab-release: [R2024b] + + steps: + - name: Checkout CanlabCore + uses: actions/checkout@v4 + with: + path: CanlabCore + + - name: Checkout Neuroimaging_Pattern_Masks + uses: actions/checkout@v4 + with: + repository: canlab/Neuroimaging_Pattern_Masks + path: Neuroimaging_Pattern_Masks + + - name: Clone SPM25 + run: git clone --depth=1 --branch 25.01.02 https://github.com/spm/spm.git "${GITHUB_WORKSPACE}/spm" + + - name: Set up MATLAB + uses: matlab-actions/setup-matlab@v2 + with: + release: ${{ matrix.matlab-release }} + # README lists Statistics and Signal Processing as required. + # Without these, functions like nanmean / filtfilt fall back to + # SPM's bundled compat shims, which is fragile. + products: | + Statistics_and_Machine_Learning_Toolbox + Signal_Processing_Toolbox + cache: true + + - name: Run unit tests + uses: matlab-actions/run-command@v2 + with: + command: | + addpath(genpath('CanlabCore')); + addpath(genpath('Neuroimaging_Pattern_Masks')); + % NB: addpath, not genpath — SPM ships compat shims under + % external/fieldtrip/compat that shadow MATLAB builtins (flip, + % isfile, etc.) and break matlab.unittest discovery. + addpath('spm'); + cd CanlabCore/CanlabCore/Unit_tests; + results = canlab_run_all_tests('JUnit', fullfile(getenv('GITHUB_WORKSPACE'), 'test-results.xml')); + if any([results.Failed]); error('CanlabCore:tests:failed', '%d failed, %d incomplete', sum([results.Failed]), sum([results.Incomplete])); end + + - name: Upload test results + if: always() + uses: actions/upload-artifact@v4 + with: + name: test-results-${{ matrix.matlab-release }} + path: test-results.xml + if-no-files-found: warn diff --git a/.github/workflows/tests-walkthroughs.yml b/.github/workflows/tests-walkthroughs.yml new file mode 100644 index 00000000..e1617ef2 --- /dev/null +++ b/.github/workflows/tests-walkthroughs.yml @@ -0,0 +1,74 @@ +name: tests-walkthroughs + +# Tier B integration tests: runs the canlab_help_* walkthroughs from +# the CANlab_help_examples repository end-to-end as a nightly job. +# These are slower and depend on more sibling repositories than the +# fast unit suite (test.yml), so they're not part of the per-push CI. + +on: + schedule: + # Run nightly at 07:00 UTC (~midnight Pacific / ~02:00 Central). + - cron: '0 7 * * *' + workflow_dispatch: + +jobs: + matlab-walkthroughs: + name: MATLAB ${{ matrix.matlab-release }} walkthroughs on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest] + matlab-release: [R2024b] + + steps: + - name: Checkout CanlabCore + uses: actions/checkout@v4 + with: + path: CanlabCore + + - name: Checkout Neuroimaging_Pattern_Masks + uses: actions/checkout@v4 + with: + repository: canlab/Neuroimaging_Pattern_Masks + path: Neuroimaging_Pattern_Masks + + - name: Checkout CANlab_help_examples + uses: actions/checkout@v4 + with: + repository: canlab/CANlab_help_examples + path: CANlab_help_examples + + - name: Clone SPM25 + run: git clone --depth=1 --branch 25.01.02 https://github.com/spm/spm.git "${GITHUB_WORKSPACE}/spm" + + - name: Set up MATLAB + uses: matlab-actions/setup-matlab@v2 + with: + release: ${{ matrix.matlab-release }} + products: | + Statistics_and_Machine_Learning_Toolbox + Signal_Processing_Toolbox + cache: true + + - name: Run walkthrough tests + uses: matlab-actions/run-command@v2 + with: + command: | + addpath(genpath('CanlabCore')); + addpath(genpath('Neuroimaging_Pattern_Masks')); + addpath(genpath('CANlab_help_examples')); + % NB: addpath, not genpath — SPM compat shims would shadow + % MATLAB builtins; see test.yml for the same rationale. + addpath('spm'); + cd CanlabCore/CanlabCore/Unit_tests; + results = canlab_run_all_tests('Walkthroughs', 'only', 'JUnit', fullfile(getenv('GITHUB_WORKSPACE'), 'walkthrough-results.xml')); + if any([results.Failed]); error('CanlabCore:walkthroughs:failed', '%d failed, %d incomplete', sum([results.Failed]), sum([results.Incomplete])); end + + - name: Upload walkthrough results + if: always() + uses: actions/upload-artifact@v4 + with: + name: walkthrough-results-${{ matrix.matlab-release }} + path: walkthrough-results.xml + if-no-files-found: warn diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..3bfb7dd0 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,116 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## What this is + +CanlabCore is a MATLAB toolbox for MRI/fMRI/PET analysis from the Cognitive and Affective Neuroscience Lab (PI: Tor Wager). The core abstraction is a small set of object types that wrap neuroimaging data and provide a consistent, high-level method surface (`plot`, `predict`, `ica`, `threshold`, `apply_atlas`, `montage`, `surface`, ...). There is **no build system, no test runner, and no linter** — code is loaded onto the MATLAB path and exercised interactively or via user scripts. + +## Setup and "running" the toolbox + +- From a directory **above** the cloned repos, run `canlab_toolbox_setup` in MATLAB. It searches for sibling CANlab repos (CanlabCore, Neuroimaging_Pattern_Masks, CANlab_help_examples, MediationToolbox, RobustToolbox, etc.), adds each to the path with subfolders, and offers to `git clone` any that are missing. +- Required dependencies: MATLAB + Statistics Toolbox + Signal Processing Toolbox + **SPM12** (https://www.fil.ion.ucl.ac.uk/spm/). SPM is used heavily for image I/O (`spm_vol`, `spm_read_vols`, `spm_orthviews`). +- The companion repo `Neuroimaging_Pattern_Masks` (already added as a working directory here) provides the atlases, signatures, and meta-analysis maps that `load_atlas` and `load_image_set` resolve by keyword. Many methods silently depend on its files being on the path. + +## Tests + +There is no test harness. What exists: +- `CanlabCore/Unit_tests/` — three standalone scripts (`check_roi_extraction.m`, `jackknife_similarity_unit_test.m`, `resampling_pattern_expression_unit_test1.m`). Run by `cd`'ing in MATLAB and calling the function name. +- `@fmri_data/predict_test_suite.m` and `@fmri_data/validate_object.m` — broader sanity checks invoked on a constructed object (e.g. `validate_object(my_fmri_data)`). +- For ad-hoc verification, the canonical pattern is to load a sample dataset (`load_image_set('emotionreg')` or files under `CanlabCore/Sample_datasets/`) and run the method end-to-end. + +## Object architecture (the part you must understand to be productive) + +Almost everything is built around a single design idea: **brain images are stored flat as a `[voxels × images]` matrix in the object's `.dat` field, with `volInfo` carrying the inverse mapping back to 3-D space.** This lets generic statistical/ML code operate on `.dat` without knowing about neuroimaging, while `reconstruct_image`, `orthviews`, `montage`, `surface`, etc. recover the spatial view on demand. + +### Class hierarchy + +`image_vector` is the abstract superclass. The classes you will actually instantiate are its subclasses: + +- **`fmri_data`** — the workhorse. Holds 4-D fMRI/PET/contrast data plus `.X` (predictors), `.Y` (outcomes), `.covariates`, `.images_per_session`, etc. Most analysis methods (`predict`, `ica`, `regress`, `searchlight`, `mahal`, `preprocess`) live here. +- **`statistic_image`** — t/p/effect-size maps. Knows about thresholds; `threshold(...)` re-thresholds without losing the underlying values. +- **`atlas`** — labeled parcellations. Has `.probability_maps`, `.labels`, `.label_descriptions`, and methods like `select_atlas_subset`, `merge_atlases`, `downsample_parcellation`, `atlas2region`. +- **`fmri_mask_image`** — binary masks (mostly legacy; many newer methods accept a plain `fmri_data` or `image_vector` as a mask). + +Other top-level classes (not subclasses of `image_vector`): +- **`region`** — list of contiguous clusters. Produced by `region(statistic_image)` and consumed by `montage`, `table`, `surface`, `extract_data`. The bridge between voxelwise maps and ROI summaries. +- **`fmridisplay`** — a registered handle bag for a montage/surface figure. Workflow is `o2 = canlab_results_fmridisplay(...)`, then `addblobs(o2, region(t))`, `removeblobs(o2)`, `addblobs(o2, ..., 'nolegend')`. The point is that the figure persists; you swap blob layers in/out without re-rendering anatomy. +- **`brainpathway` / `brainpathway_multisubject`** — connectivity / pathway-modeling objects. +- **`canlab_dataset`** — generic subject × variable behavioral/clinical data container with its own `glm`, `mediation`, `scatterplot`, etc. +- **`fmri_glm_design_matrix`**, **`fmri_timeseries`**, **`predictive_model`** — specialized containers for design matrices, raw timeseries, and ML model artifacts. + +### MATLAB `@class` directories + +Each class lives in `CanlabCore/@/`. Files in that directory are methods of that class, dispatched via the first argument. The constructor is `@classname/classname.m`. **Adding a method = dropping a `.m` file into the `@class/` folder** with `function out = methodname(obj, ...)`. There is no methods block to edit; `methods(obj)` discovers them from disk. Because `fmri_data`, `statistic_image`, and `atlas` all subclass `image_vector`, methods defined in `@image_vector/` are inherited by all of them — a method only needs to be redefined in a subclass directory if its behavior differs. + +### Provenance and "removed" bookkeeping + +Two invariants that recur across nearly every method: + +1. **`history`** — a cell array of strings appended to by methods. New methods that mutate the object should push a one-line description. +2. **`removed_voxels` / `removed_images`** — when voxels or images are dropped (e.g. `remove_empty`, `apply_mask`), the object shrinks `.dat` and records which rows/columns were removed. `replace_empty(obj)` re-expands `.dat` to the original shape, padded with zeros, so downstream code that needs full-space indexing can rely on it. Many bugs in this codebase historically came from forgetting to call `replace_empty` or `remove_empty` at the right point — when in doubt, call `replace_empty` before reasoning about voxel positions and `remove_empty` before doing math across `.dat` rows. + +## Canonical workflows (use these as templates) + +```matlab +% Group analysis end-to-end +imgs = load_image_set('emotionreg'); % fmri_data with sample images +plot(imgs); descriptives(imgs); % QC +t = ttest(imgs); % statistic_image +t = threshold(t, .005, 'unc', 'k', 10); % cluster-extent threshold +r = region(t); % region object, one per blob +table(t); % printed/atlas-labeled results +o2 = canlab_results_fmridisplay(t, 'full'); % registered montage+surface figure +montage(r, 'regioncenters', 'colormap'); % per-blob mini-montage +``` + +```matlab +% ROI extraction against an atlas +atl = load_atlas('canlab2024'); % keyword-resolved atlas +parcel_means = apply_parcellation(imgs, atl); % images x parcels +``` + +```matlab +% Cross-validated prediction +[cv, stats, optout] = predict(imgs, 'algorithm_name','cv_lassopcr', 'nfolds',5); +``` + +## Layout + +- `CanlabCore/@*/` — the object classes described above. +- `CanlabCore/Statistics_tools/`, `Visualization_functions/`, `Data_processing_tools/`, `Image_thresholding/`, `Model_building_tools/`, `Reporting/` — function libraries called by the class methods. Edit here when a method's logic is not class-specific. +- `CanlabCore/Data_extraction/` — `load_atlas.m`, `load_image_set.m` (keyword resolvers), `extract_*` helpers. +- `CanlabCore/GLM_Batch_tools/` — `canlab_glm_subject_levels` / `canlab_glm_group_levels`, an SPM12-driven first/second-level batch system. Driven by a `DSGN` struct; see `canlab_glm_dsgninfo.txt` and `canlab_glm_README.txt`. +- `CanlabCore/HRF_Est_Toolbox2/` and `HRF_Est_Toolbox4/` — Lindquist-lab HRF estimation (Logit, sFIR, spline, canonical). +- `CanlabCore/OptimizeDesign11/` — genetic-algorithm fMRI design optimization. +- `CanlabCore/Cifti_plotting/`, `Parcellation_tools/`, `Cluster_contig_region_tools/`, `ROI_drawing_tools/`, `Image_space_tools/`, `Image_computation_tools/` — domain-specific helpers. +- `CanlabCore/External/` — vendored third-party toolboxes (`matlab_bgl`, `spider`, `lasso`, `boundedline`, `export_fig`, `BCT`, `umap`, ...). Treat as read-only; don't refactor. +- `CanlabCore/Sample_datasets/` — small datasets used by examples and walkthroughs. +- `CanlabCore/Unit_tests/` — sparse standalone test scripts (see Tests above). +- `nipype/`, `docs/`, `docs_sphinx_old/` — Python wrappers and old docs; rarely touched. + +## Conventions worth knowing + +- **First argument is always the object** (`function out = method(obj, varargin)`); methods are typically called as `method(obj, ...)` rather than `obj.method(...)`, though both work. +- **`varargin` keyword pairs** are the universal option style. Existing methods use a hand-rolled `for i=1:length(varargin), switch varargin{i}, case 'foo', foo = varargin{i+1};` loop. **New functions should use `inputParser` instead** — see the next section. +- **Many methods accept either a filename, an `fmri_data`, or another `image_vector` subclass** as their "image-like" argument and dispatch via `isa(...)`. Preserve that polymorphism when editing. +- **Spatial alignment is not implicit.** Methods that combine two image objects generally either error or call `resample_space(a, b)` first; if you write a new combiner, do the same — don't assume two objects share `volInfo`. +- **`.asv` files are MATLAB autosave artifacts** and are gitignored; ignore them. A few committed `*_old.m` files are intentional legacy fallbacks (e.g. `region2imagevec_old.m`, `predictive_model_old.m`) — don't delete them without checking callers. + +## When writing new functions + +These rules apply to new code. Existing code does not need to be retrofitted. + +1. **Name stand-alone functions `canlab_`.** This namespaces the function so it does not collide with future external toolboxes the user may add to their path. Class methods (files inside `@class/`) are exempt — they're already namespaced by the class. + +2. **Match the documentation format in `CanlabCore/Misc_utilities/documentation_template.m`.** That template defines the section ordering (Usage, Inputs, Outputs, Examples, References, etc.) and comment style readthedocs expects. Open it before writing the help block; copy the structure rather than improvising. + +3. **Use `inputParser` for variable input arguments**, following the `INPUT PARSER TEMPLATE` section of `documentation_template.m`. Retain the explanatory comments inside that block — they're a teaching scaffold for future readers, not noise. Implement the `'plot'`, `'verbose'`, `'doplot'`, and `'doverbose'` parameters whenever the function has plotting or chatter that the caller might want to suppress. + +4. **Include a runnable example in the help block** that loads or creates a test dataset (e.g. `load_image_set('emotionreg')`, `sim_data`, or a synthetic array) and demonstrates the function with a few of the most common options. The example should be copy-pasteable: someone with CanlabCore on their path should be able to highlight it and run it. + +## Documentation pointers + +- Function-by-function reference: https://canlabcore.readthedocs.org/en/latest/ +- Walkthroughs and batch-script examples (the best way to learn the API): https://github.com/canlab/CANlab_help_examples +- Lab landing page: https://canlab.github.io diff --git a/CanlabCore/@atlas/assign_vals.m b/CanlabCore/@atlas/assign_vals.m index c52a470e..dd9084c1 100644 --- a/CanlabCore/@atlas/assign_vals.m +++ b/CanlabCore/@atlas/assign_vals.m @@ -1,19 +1,58 @@ function [dat, tbl] = assign_vals(atl, varargin) -% ASSIGN_VALS Assigns numeric values to atlas regions and creates an fmri_data object. +% assign_vals Assign numeric values to atlas regions and create an fmri_data object. % -% [dat, tbl] = assignvals2atlas(atl, 'reg_names', reg_names, 'vals', vals, 'sort', true) +% Take an atlas object and a vector of values associated with named +% regions, and produce an fmri_data object whose voxel values are the +% assigned region value, plus a table mapping each region name to its +% assigned value. Region names are matched against the atlas region +% shorttitles (as produced by atlas2region). % -% Inputs: -% atl - An atlas structure (with fields like .labels, .data) -% reg_names - Cell array of region names (must match atlas region shorttitles) -% vals - Numeric values (same length as reg_names) -% sort - (Optional) Logical flag to sort output table by value (default = true) +% :Usage: +% :: % -% Outputs: -% dat - fmri_data object with assigned region values -% tbl - Table of regions and assigned values +% [dat, tbl] = assign_vals(atl, 'reg_names', reg_names, 'vals', vals, 'sort', true) % -% Author: Michael Sun, Ph.D. 4/23/2025 +% :Inputs: +% +% **atl:** +% An atlas-class object (with fields .labels, .dat, etc.). +% +% :Optional Inputs: +% +% **'reg_names':** +% Cell array (or string array) of region names to assign values to. +% Names must match atlas region shorttitles. Default: atl.labels. +% +% **'vals':** +% Numeric vector of values to assign, the same length as reg_names. +% Default: zeros(numel(atl.labels), 1). +% +% **'sort':** +% Logical flag to sort output table by value in descending order. +% Default: true. +% +% :Outputs: +% +% **dat:** +% fmri_data object with each region's voxel values set to the +% assigned value. +% +% **tbl:** +% MATLAB table of region shorttitles and assigned values. +% +% :Examples: +% :: +% +% [dat, tbl] = assign_vals(atl, 'reg_names', {'Reg1' 'Reg2'}, ... +% 'vals', [1 2], 'sort', true); +% +% :See also: +% - atlas2region +% - region2fmri_data +% +% .. +% Author: Michael Sun, Ph.D. 4/23/2025 +% .. % Parse optional inputs parser = inputParser; diff --git a/CanlabCore/@atlas/atlas.m b/CanlabCore/@atlas/atlas.m index a810b2af..4a6e0ae3 100644 --- a/CanlabCore/@atlas/atlas.m +++ b/CanlabCore/@atlas/atlas.m @@ -1,146 +1,215 @@ -% atlas: Subclass of image_vector designed for brain atlases and parcellations +% atlas Subclass of image_vector designed for brain atlases and parcellations. % -% 'atlas' is a data class containing information about brain atlases and parcellations -% stored in a structure-like object. It inherits the properties and -% methods of fmri_data and image_vector objects. +% 'atlas' is a data class containing information about brain atlases and +% parcellations stored in a structure-like object. It inherits the +% properties and methods of fmri_data and image_vector objects. % % 'atlas' objects are a class of objects specially designed for brain % atlases. They have properties (fields) for probabilistic maps and a data % field (.dat) that contains integer codes for thresholded/maximum -% probability labels. There is also a "labels" property with the text +% probability labels. There is also a 'labels' property with the text % labels for each region, and additional description and label fields for -% additional annotation. These can hold, e.g., hierarchical labels at different -% levels of spatial resolution. A "reference" property holds information -% about associated publications. +% additional annotation. These can hold, e.g., hierarchical labels at +% different levels of spatial resolution. A 'reference' property holds +% information about associated publications. % % Atlas objects have specialized methods for selecting regions by name or -% number (including groups of regions with similar names). Because it is a -% subclass of the image_vector object, it inherits all of its methods as +% number (including groups of regions with similar names). Because it is a +% subclass of the image_vector object, it inherits all of its methods as % well (montage, surface, apply_mask, write, descriptives, flip, -% image_similarity_plot, image_math, etc. +% image_similarity_plot, image_math, etc.). % % The function load_atlas in the CANlab toolbox loads a number of named -% atlases included with the toolbox. Type >> help load_atlas for a list of +% atlases included with the toolbox. Type 'help load_atlas' for a list of % named atlases. -% -% Creating an atlas object requires either images with probability maps (a 4-d image) -% Or an integer-valued image with one integer per atlas region. -% For full functionality, the atlas also requires both probability maps and -% text labels, one per region, in a cell array. But some functionality will -% work without these. -% -% Basic usage for creating a new atlas image: -% obj = atlas(image_names, ['mask',maskinput], [other optional inputs]) -% -% maskinput : Name of mask image to use. Default: 'brainmask.nii', a -% brain mask that is distributed with SPM software -% Alternative in CANlab tools: which('gray_matter_mask.img') -% 'noverbose' : Suppress verbose output -% 'sample2mask' : Sample images to mask space. Default: Sample mask to -% image space, use native image space -% -% -% Creating class instances -% ----------------------------------------------------------------------- +% +% Creating an atlas object requires either images with probability maps (a +% 4-d image) or an integer-valued image with one integer per atlas region. +% For full functionality, the atlas also requires both probability maps +% and text labels, one per region, in a cell array. But some functionality +% will work without these. +% +% :Usage: +% :: +% +% obj = atlas +% obj = atlas(image_names, ['mask', maskinput], [other optional inputs]) +% +% :Inputs: +% +% **image_names:** +% Character/cell array of image filenames, an image_vector object, +% or empty. Probability maps (4-D), an integer-valued parcellation +% image, or an existing image_vector are accepted. +% +% :Optional Inputs: +% +% **'mask', maskinput:** +% Name of mask image to use. Default: 'brainmask.nii', a brain mask +% distributed with SPM software. Alternative in CANlab tools: +% which('gray_matter_mask.img'). +% +% **'sample2mask':** +% Sample images to mask space. Default: sample mask to image space, +% use native image space. +% +% **'noverbose':** +% Suppress verbose output. +% +% **'atlas_name':** +% Followed by a short description or name of the atlas. +% +% **'labels':** +% Followed by a cell array of text strings, one per region. +% +% **'label_descriptions':** +% Followed by a regions x 1 cell array of long-form descriptions. +% +% **'labels_2', 'labels_3', 'labels_4', 'labels_5':** +% Additional cell arrays of labels, one per region, often used for +% hierarchical labels at different levels of granularity. +% +% **'references':** +% Followed by a string matrix of associated publications. +% +% **'space_description':** +% Followed by a string describing the atlas space/template. +% Setting this to a value supported by render_on_surface +% (e.g., 'MNI152NLin2009cAsym') enables automatic projection to +% supported surfaces when plotting. +% +% :Outputs: +% +% **obj:** +% An atlas-class object with fields populated from the input +% images and optional arguments. +% +% :Creating class instances: +% % You can create an empty object by using: -% obj = atlas -% - obj is the object. -% - It will be created with a standard brain mask, brainmask.nii -% - This image should be placed on your Matlab path -% - The space information is stored in obj.volInfo -% - Data is stored in obj.dat, in a [voxels x images] matrix +% :: +% +% obj = atlas +% +% - It will be created with a standard brain mask, brainmask.nii. +% - This image should be placed on your Matlab path. +% - The space information is stored in obj.volInfo. +% - Data is stored in obj.dat, in a [voxels x images] matrix. % - You can replace or append data to the obj.dat field. % -% You can create an atlas object with extacted image data. -% - Let "imgs" be a string array or cell array of image names -% - This command creates an object with your (4-D) image data: -% - fmri_dat = atlas(imgs); +% You can create an atlas object with extracted image data: +% :: +% +% fmri_dat = atlas(imgs); +% +% - imgs is a string array or cell array of image names. % - Only values in the standard brain mask, brainmask.nii, will be included. % - This saves memory by reducing the number of voxels saved dramatically. % -% You can specify any mask you'd like to extract data from. -% - Let "maskimagename" be a string array with a mask image name. -% - this command creates the object with data saved in the mask: -% - fmri_dat = atlas(imgs, maskimagename); -% - The mask information is saved in fmri_dat.mask -% -% e.g., this extracts data from images within the standard brain mask: -% dat = atlas(imgs, which('brainmask.nii')); -% -% Properties and methods -% ----------------------------------------------------------------------- -% Properties are data fields associated with an object. -% Try typing the name of an object (class instance) you create to see its -% properties, and a link to its methods (things you can run specifically -% with this object type). For example: After creating an atlas object +% You can specify any mask you would like to extract data from: +% :: +% +% fmri_dat = atlas(imgs, maskimagename); +% +% e.g., to extract data from images within the standard brain mask: +% :: +% +% dat = atlas(imgs, which('brainmask.nii')); +% +% :Properties and methods: +% +% Properties are data fields associated with an object. Try typing the +% name of an object (class instance) you create to see its properties, +% and a link to its methods. For example: After creating an atlas object % called fmri_dat, as above, type fmri_dat to see its properties. % -% There are many other methods that you can apply to atlas objects to -% do different things. +% There are many other methods that you can apply to atlas objects to do +% different things. +% % - Try typing methods(atlas) for a list. % - You always pass in an atlas object as the first argument. % - Methods include utilities for many functions - e.g.,: % - resample_space(fmri_dat) resamples the voxels -% - write(fmri_dat) writes an image file to disk (careful not to overwrite by accident!) +% - write(fmri_dat) writes an image file to disk % - regress runs multiple regression % - predict runs cross-validated machine learning/prediction algorithms % % Specialized methods unique to atlas objects include: -% atlas Construct a new atlas object given Analyze/Nifti image(s) -% +% +% - atlas: Construct a new atlas object given Analyze/Nifti image(s) +% % Utilities for manipulating atlases: -% merge_atlases Add all or some regions from an atlas object to another atlas object (with/without replacing existing labeled voxels) -% probability_maps_to_region_index Use dat.probability_maps to rebuild integer vector of index labels (dat.dat) -% remove_atlas_region Removes region(s) from atlas, by names or index values -% reorder_atlas_regions Reorder a set of regions in an atlas object -% select_atlas_subset Select a subset of regions in an atlas by name or integer code, with or without collapsing regions together -% split_atlas_by_hemisphere Divide regions that are bilateral into separate left- and right-hemisphere regions -% split_atlas_into_contiguous_regions Divide regions with multiple contiguous blobs into separate labeled regions for each blob -% threshold Threshold atlas object based on values in obj.probability_maps property -% -% % Extracting information and converting to other object types: -% extract_data Extract atlas parcel means and local pattern responses from a set of data images -% atlas2region Convert an atlas object to a region object -% check_properties Check properties and enforce some variable types -% get_region_volumes Get the volume (and raw voxel count) of each region in an atlas object -% num_regions Count number of regions in atlas object, even with incomplete data -% +% +% - merge_atlases: Add regions from an atlas object to another atlas object +% - probability_maps_to_region_index: Rebuild integer index labels from probability_maps +% - remove_atlas_region: Remove region(s) by names or index values +% - reorder_atlas_regions: Reorder a set of regions in an atlas object +% - select_atlas_subset: Select regions by name or integer code +% - split_atlas_by_hemisphere: Divide bilateral regions into L and R +% - split_atlas_into_contiguous_regions: Split contiguous blobs into separate regions +% - threshold: Threshold atlas based on values in obj.probability_maps +% +% Extracting information and converting to other object types: +% +% - extract_data: Extract atlas parcel means and local pattern responses +% - atlas2region: Convert an atlas object to a region object +% - check_properties: Check properties and enforce variable types +% - get_region_volumes: Get volume and voxel count of each region +% - num_regions: Count regions in atlas object, even with incomplete data +% % Manipulating labels for atlas regions: -% atlas_add_L_R_to_labels Removes some strings indicating lateralization from atlas labels and adds new _L and _R suffixes for lateralized regions -% atlas_similarity Annotate regions in an atlas object with labels from another atlas object -% +% +% - atlas_add_L_R_to_labels: Add _L/_R suffixes for lateralized regions +% - atlas_similarity: Annotate regions with labels from another atlas +% % Visualization: -% isosurface Create a series of surfaces in different colors, one for each region -% montage Display an atlas object on a standard slice montage % -% Attaching additional data -% ----------------------------------------------------------------------- -% The atlas object has a number of fields for appending specific types of data. +% - isosurface: Create a series of surfaces in different colors per region +% - montage: Display an atlas object on a standard slice montage +% +% :Attaching additional data: +% +% The atlas object has a number of fields for appending specific types of +% data. % -% - You can replace data in the atlas.dat field. However, should always contain one column vector of integers. +% - You can replace data in the atlas.dat field. However, it should always +% contain one column vector of integers. % - Attach custom descriptions in several fields to document your object. -% - The "history" field stores a cell array of strings with the processing -% history of the object. Some methods add to this history automatically. -% -% -% Examples: -% ----------------------------------------------------------------------- -% parcellation_file = 'CIT168toMNI152_prob_atlas_bilat_1mm.nii'; %'prob_atlas_bilateral.nii'; -% labels = {'Put' 'Cau' 'NAC' 'BST_SLEA' 'GPe' 'GPi' 'SNc' 'RN' 'SNr' 'PBP' 'VTA' 'VeP' 'Haben' 'Hythal' 'Mamm_Nuc' 'STN'}; -% dat = atlas(which(parcellation_file), 'labels', labels, ... -% 'space_description', 'MNI152 space', ... -% 'references', 'Pauli 2018 Bioarxiv: CIT168 from Human Connectome Project data', 'noverbose'); -% % Display: -% orthviews(dat); -% figure; montage(dat); -% -% % Convert to region object and display: -% r = atlas2region(dat); -% orthviews(r) -% montage(r); - -% Programmers' notes: -% Tor Wager, 1/14/18 : Created from hybrid of image_vector (parent) and some fmri_data code +% - The 'history' field stores a cell array of strings with the processing +% history of the object. Some methods add to this history automatically. +% +% :Examples: +% :: +% +% parcellation_file = 'CIT168toMNI152_prob_atlas_bilat_1mm.nii'; +% labels = {'Put' 'Cau' 'NAC' 'BST_SLEA' 'GPe' 'GPi' 'SNc' 'RN' ... +% 'SNr' 'PBP' 'VTA' 'VeP' 'Haben' 'Hythal' 'Mamm_Nuc' 'STN'}; +% dat = atlas(which(parcellation_file), 'labels', labels, ... +% 'space_description', 'MNI152 space', ... +% 'references', 'Pauli 2018 Bioarxiv: CIT168 from HCP data', ... +% 'noverbose'); +% +% % Display: +% orthviews(dat); +% figure; montage(dat); +% +% % Convert to region object and display: +% r = atlas2region(dat); +% orthviews(r) +% montage(r); +% +% :See also: +% - load_atlas +% - image_vector +% - fmri_data +% - region +% - atlas2region +% +% .. +% Programmers' notes: +% Tor Wager, 1/14/18: Created from hybrid of image_vector (parent) and +% some fmri_data code. +% .. classdef atlas < image_vector @@ -384,16 +453,12 @@ % Now extract the actual data from the mask switch spm('Ver') - - case {'SPM25', 'SPM12','SPM8', 'SPM5'} - imgdat = iimg_get_data(maskobj.volInfo, image_names, 'single', verbosestr, 'noexpand'); - case {'SPM2', 'SPM99'} - % legacy, for old SPM + % legacy SPM imgdat = iimg_get_data(maskobj.volInfo, image_names, 'single', verbosestr); - otherwise - error('Unknown version of SPM! Update code, check path, etc.'); + % SPM5+, including any future versions + imgdat = iimg_get_data(maskobj.volInfo, image_names, 'single', verbosestr, 'noexpand'); end imgdat = imgdat'; diff --git a/CanlabCore/@atlas/atlas2region.m b/CanlabCore/@atlas/atlas2region.m index 71ecf7d5..dbc66f9b 100644 --- a/CanlabCore/@atlas/atlas2region.m +++ b/CanlabCore/@atlas/atlas2region.m @@ -1,38 +1,77 @@ function r = atlas2region(atlas_obj, varargin) -% Convert an atlas object to a region object -% r = atlas2region(atlas_obj) -% -% Note: There are multiple potential ways this can work. -% The default is to use uses the atlas_obj.dat field, -% which has one label per voxel. For some atlases, a region may have no -% voxels with the max probability label. -% -% 'use_probabilities' uses atlas_obj.probability maps if available, -% which guarantees at least one region per atlas region. If atlas.probability_maps is -% empty, however, the function defaults to using index labels. -% Note: this can be slow for large atlases. -% -% Another default is to parse regions with each label into contiguous -% regions. This can result in multiple contiguous regions in the region object (r) -% for a single atlas region. (e.g., those in left and right hemispheres, if -% they are given the same label in the atlas). -% -% 'nocontiguous' (or 'unique_mask_values') is an option that suppresses the -% parsing, creating one region per label/atlas region. -% If atlas.probability_maps is empty or 'use_labels' is specified, -% the function defaults to 'nocontiguous'. -% -% Examples: -% r = atlas2region(atlas_obj); % Use indices in dat.dat, one region per atlas region. -% r = atlas2region(atlas_obj, 'use_probabilities'); % Use probability_maps if available -% r = atlas2region(atlas_obj, 'nocontiguous'); % One region per atlas region -% -% Tor Wager, Jan 2018 - -% Programmers' notes: -% 9/24/21: region labels were wrong in some cases, if atlas object has been - % altered to remove some regions. Tor fixed this to use region.val - % mode. +% atlas2region Convert an atlas object to a region object. +% +% By default, uses the atlas_obj.dat field, which has one label per voxel. +% For some atlases, a region may have no voxels with the max-probability +% label. +% +% The default is to parse regions with each label into contiguous +% regions, which can result in multiple contiguous regions in the region +% object (r) for a single atlas region (e.g., separate left- and right- +% hemisphere regions sharing the same atlas label). +% +% :Usage: +% :: +% +% r = atlas2region(atlas_obj, [optional inputs]) +% +% :Inputs: +% +% **atlas_obj:** +% An atlas-class object. +% +% :Optional Inputs: +% +% **'use_probabilities':** +% Use atlas_obj.probability_maps if available, which guarantees at +% least one region per atlas region. If atlas.probability_maps is +% empty, the function defaults to using index labels. Note: this +% can be slow for large atlases. +% +% **'nocontiguous' or 'unique_mask_values':** +% Suppress contiguous-region parsing, creating one region per +% label/atlas region. If atlas.probability_maps is empty or +% 'use_labels' is specified, the function defaults to +% 'nocontiguous'. +% +% **image_vector object:** +% If passed in, image values are extracted into the region .val and +% .Z fields. +% +% **'noverbose':** +% Suppress warnings and progress messages. +% +% :Outputs: +% +% **r:** +% A region-class object array with fields populated from the +% atlas labels and (if provided) extracted image values. +% +% :Examples: +% :: +% +% % Use indices in dat.dat, one region per atlas region: +% r = atlas2region(atlas_obj); +% +% % Use probability_maps if available: +% r = atlas2region(atlas_obj, 'use_probabilities'); +% +% % One region per atlas region (no contiguous parsing): +% r = atlas2region(atlas_obj, 'nocontiguous'); +% +% :See also: +% - region +% - select_atlas_subset +% - region2atlas +% +% .. +% Tor Wager, Jan 2018 +% +% Programmers' notes: +% 9/24/21: region labels were wrong in some cases, if atlas object has +% been altered to remove some regions. Tor fixed this to use region.val +% mode. +% .. % Defaults and inputs % ---------------------------------------------------------------------------- diff --git a/CanlabCore/@atlas/atlas_add_L_R_to_labels.m b/CanlabCore/@atlas/atlas_add_L_R_to_labels.m index b3289b93..6bb6972d 100644 --- a/CanlabCore/@atlas/atlas_add_L_R_to_labels.m +++ b/CanlabCore/@atlas/atlas_add_L_R_to_labels.m @@ -1,6 +1,32 @@ function atlas_obj = atlas_add_L_R_to_labels(atlas_obj) -% Removes some strings indicating lateralization from atlas labels and adds new _L and _R suffixes for lateralized regions. -% - strings replaced are L_ R_ Left_ Right_ _L _R _Left _Right +% atlas_add_L_R_to_labels Standardize lateralization suffixes on atlas labels. +% +% Remove some strings indicating lateralization from atlas labels and add +% new _L and _R suffixes for lateralized regions. The strings stripped from +% the labels are: L_ R_ Left_ Right_ _L _R _Left _Right. New suffixes are +% determined by the modal sign of the x-coordinate for voxels in each +% region (negative = left, positive = right). Regions whose proportional +% L/R asymmetry is below 0.5 are left without a suffix. +% +% :Usage: +% :: +% +% atlas_obj = atlas_add_L_R_to_labels(atlas_obj) +% +% :Inputs: +% +% **atlas_obj:** +% An atlas-class object whose .labels field will be modified. +% +% :Outputs: +% +% **atlas_obj:** +% Atlas object with relabeled .labels (with _L/_R suffixes added +% as appropriate). +% +% :See also: +% - split_atlas_by_hemisphere +% - atlas2region labels = atlas_obj.labels; diff --git a/CanlabCore/@atlas/atlas_similarity.m b/CanlabCore/@atlas/atlas_similarity.m index c03de870..b4b5fae8 100644 --- a/CanlabCore/@atlas/atlas_similarity.m +++ b/CanlabCore/@atlas/atlas_similarity.m @@ -1,91 +1,105 @@ function [region_table, table_legend_text, all_regions_covered, x_counts, x_dice, x_atlas_coverage, all_regions_covered_cell] = atlas_similarity(atlas_to_parse, ref_atlas_obj) -% Annotate regions in an atlas object with labels from another atlas object -% Take regions in an atlas object (atlas_to_parse) and annotate them with labels and quantitative -% coverage stats from another atlas (ref_atlas_obj) +% atlas_similarity Annotate regions in an atlas object with labels from another atlas object. % -% [region_table, table_legend_text, coverage25_labels, coverage25_index, x_counts, x_dice, x_atlas_coverage] = atlas_similarity(atlas_to_parse, ref_atlas_obj) +% Take regions in an atlas object (atlas_to_parse) and annotate them with +% labels and quantitative coverage stats from another atlas +% (ref_atlas_obj). Computes Dice similarities, cross-counts, modal labels, +% and coverage matrices for each target region. % -% Dice: Compute similarities (cross-counts and Dice coeffs) between parcels in two atlases -% matrix of [atlas1] x [atlas2] +% :Usage: +% :: % -% Mode: Labels each region in a target atlas (atlas 1) with best-matching (modal) label -% in a reference atlas (atlas 2). -% - modal_atlas_index is the reference atlas index number for each target region -% - modal_atlas_label is the reference atlas label for each target region, or "No -% region identified" if the target region does not match any reference -% atlas region. +% [region_table, table_legend_text, all_regions_covered, x_counts, ... +% x_dice, x_atlas_coverage, all_regions_covered_cell] = ... +% atlas_similarity(atlas_to_parse, ref_atlas_obj) % -% Coverage: Return matrix of coverage of reference atlas regions for each -% target region. For each target (row), values are the proportion of -% reference region (column) covered. -% - modal_atlas_coverage is the proportion of reference atlas voxels -% covered by the best-matching region +% :Inputs: % -% all_regions_covered: String matrix list of all reference regions covered by the target region down to 25% coverage, sorted in descending order of coverage -% all_regions_covered_cell: Cell array of all regions covered +% **atlas_to_parse:** +% Atlas-class object whose regions are to be annotated. % -% See table_legend_text output for description of table entries. -% to print legend: canlab_print_legend_text(table_legend_text{:}) +% **ref_atlas_obj:** +% Reference atlas-class object providing the labels and +% coordinates against which atlas_to_parse is annotated. % -% Tor Wager, July 2018 - - - -% Notes: -% - Updated Feb 2020 by Tor Wager - added all_regions_covered_cell - -% Percent of voxels in each atlas region covered by the blob -% Intersection / size of atlas region - -% note: vox counts can be zero if regions are (1) outside mask, or (2) -% empty after reslicing to the atlas space - -% "coverage" : which atlas regions are covered by the blob? (up to specified % of voxels -% in the atlas region). Large atlas regions/networks will not have high -% coverage unless the blob(s) activate most of the atlas region/network. -% +% :Outputs: +% +% **region_table:** +% MATLAB table with one row per region in atlas_to_parse, with +% columns describing region size, modal reference label, coverage, +% and a list of all reference regions covered. +% +% **table_legend_text:** +% Cell array of strings describing the columns of region_table +% and the references used. Print with canlab_print_legend_text. +% +% **all_regions_covered:** +% Cell array of char matrices listing all reference regions +% covered by each target region down to 25% coverage, sorted in +% descending order of coverage. +% +% **x_counts:** +% [n_target x n_ref] matrix of cross-counts (intersection voxel +% counts) between target and reference regions. +% +% **x_dice:** +% [n_target x n_ref] matrix of Dice similarity coefficients. +% +% **x_atlas_coverage:** +% [n_target x n_ref] matrix giving, for each target region (row), +% the proportion of each reference region (column) covered. +% +% **all_regions_covered_cell:** +% Cell array of cell arrays of all reference region labels covered +% by each target region, useful for programmatic access. +% +% :Notes: % -% "mode" : what single atlas region best encloses the target blob? -% if blob covers 2 regions completely, the larger is the best match +% - 'Coverage': which atlas regions are covered by the blob (up to a +% specified percent of voxels in the atlas region). Large atlas +% regions/networks will not have high coverage unless the blob(s) +% activate most of the atlas region/network. +% - 'Mode': what single atlas region best encloses the target blob? +% If blob covers 2 regions completely, the larger is the best match. +% - 'Similarity' (jaccard/dice): prioritizes complete coverage of atlas +% region. Larger regions tend not to show up unless the blob covers +% them completely. +% - Multi-resolution matching with Jaccard could select small regions +% when they match well, but larger/more general regions when the blob +% description matches them best. For example, a big region that covers +% the whole basal ganglia would be labeled 'BG'. Mode would pick the +% largest subregion. Coverage would identify multiple subregions. +% - Voxel counts can be zero if regions are (1) outside mask, or (2) +% empty after reslicing to the atlas space. % -% "similarity" : jaccard/dice: prioritizes complete coverage of atlas region. -% larger regions will tend to not show up unless the blob -% covers them completely. +% Dice similarity coefficient of two sets A and B: +% :: % -% could do multi-resolution match with jaccard. small regions would be -% selected if they match, but larger/more general regions would be selected -% if the blob description matches them best. -% e.g., a big region that covers the whole basal ganglia would be labeled -% "BG". "Mode" would pick the largest subregion. "Coverage" would identify -% multiple subregions. - -% Notes - from Matlab -% ----- -% [1] The Dice similarity coefficient of two sets A and B is -% expressed as +% dice(A,B) = 2 * |intersection(A,B)| / (|A| + |B|) +% = 2 * TP / (2 * TP + FP + FN) +% = 2 * jaccard(A,B) / (1 + jaccard(A,B)) % -% dice(A,B) = 2 * |intersection(A,B)| / (|A| + |B|) +% Jaccard similarity coefficient (intersection over union): +% :: % -% where |A| represents the cardinal of set A. It can also be expressed in -% terms of true positives (TP), false positives (FP) and false negatives -% (FN) as +% jaccard(A,B) = |intersection(A,B)| / |union(A,B)| +% = TP / (TP + FP + FN) % -% dice(A,B) = 2 * TP / (2 * TP + FP + FN) +% :Examples: +% :: % -% [2] The Dice index is related to the Jaccard index according to +% [region_table, table_legend_text] = atlas_similarity(atlas_to_parse, ref_atlas_obj); +% canlab_print_legend_text(table_legend_text{:}); % -% dice(A,B) = 2 * jaccard(A,B) / (1 + jaccard(A,B)) +% :See also: +% - get_region_volumes +% - select_atlas_subset +% - atlas2region % -% [1] The Jaccard similarity coefficient of two sets A and B (also known -% as intersection over union or IoU) is expressed as -% -% jaccard(A,B) = |intersection(A,B)| / |union(A,B)| -% -% where |A| represents the cardinal of set A. It can also be expressed in -% terms of true positives (TP), false positives (FP) and false negatives -% (FN) as -% -% jaccard(A,B) = TP / (TP + FP + FN) +% .. +% Tor Wager, July 2018 +% Updated Feb 2020 by Tor Wager - added all_regions_covered_cell. +% .. coverage_thresh = .25; % percent of reference atlas region that must be covered to include it in lists % Note: if you change this, the help and figure legend and variable names will be wrong/misleading. diff --git a/CanlabCore/@atlas/check_properties.m b/CanlabCore/@atlas/check_properties.m index 5cc7fba4..bd535af5 100644 --- a/CanlabCore/@atlas/check_properties.m +++ b/CanlabCore/@atlas/check_properties.m @@ -1,14 +1,51 @@ function [obj, has_pmaps, has_index, missing_regions] = check_properties(obj, varargin) -% Check properties and enforce some variable types +% check_properties Check atlas object properties and enforce variable types. % -% obj = check_properties(obj) +% Validate and clean up the fields of an atlas object: enforce sparse +% double probability maps, integer-valued .dat with placeholder labels +% and descriptions for any missing entries, consistent row/column shape +% for label fields, and (optionally) a compressed/consecutive integer +% index when regions are missing. % -% Optional arguments: -% 'compress_index' : if index numbers are not consecutive integers, some functions, -% like select_atlas_subset and num_regions, will not work -% This rebuilds the index. This could happen after resampling or masking. -% If you have probability maps entered, probability_maps_to_region_index will do -% this. But if not, you may want to do this. +% :Usage: +% :: +% +% [obj, has_pmaps, has_index, missing_regions] = check_properties(obj, [optional inputs]) +% +% :Inputs: +% +% **obj:** +% An atlas-class object. +% +% :Optional Inputs: +% +% **'compress_index':** +% If index numbers in obj.dat are not consecutive integers, some +% functions (select_atlas_subset, num_regions) will not work +% correctly. This option rebuilds the index, which is useful after +% resampling or masking. If probability maps are present, +% probability_maps_to_region_index already performs this; otherwise +% you may want to use this flag. +% +% :Outputs: +% +% **obj:** +% Atlas object with cleaned-up properties. +% +% **has_pmaps:** +% Logical: true if obj has valid probability_maps with the right +% number of columns. +% +% **has_index:** +% Logical: true if obj.dat is non-empty. +% +% **missing_regions:** +% Vector of region index values that have no assigned voxels. +% +% :See also: +% - num_regions +% - probability_maps_to_region_index +% - select_atlas_subset % Defaults diff --git a/CanlabCore/@atlas/create.m b/CanlabCore/@atlas/create.m index 8f6f04e4..cc9bbae6 100644 --- a/CanlabCore/@atlas/create.m +++ b/CanlabCore/@atlas/create.m @@ -1,13 +1,37 @@ function obj = create(obj, varargin) +% create Populate an atlas object from fieldname/value pairs. +% % Create an object from an empty obj structure, assigning fieldname/value -% pairs as optional arguments. +% pairs as optional arguments. For known field names, the corresponding +% obj property is set to the supplied value; the 'dat' field is given +% special handling so that NaN entries are silently converted to zero. +% +% This method is used internally by the fmri_data and atlas class +% constructors. % % :Usage: % :: % -% [obj = create(obj, varargin) +% obj = create(obj, fieldname1, value1, fieldname2, value2, ...) +% +% :Inputs: +% +% **obj:** +% An atlas-class (or fmri_data) object to be populated. +% +% **varargin:** +% Pairs of (fieldname, value) where fieldname is a property of +% obj. The optional keyword 'noverbose' suppresses progress +% messages. +% +% :Outputs: +% +% **obj:** +% The input object with specified fields populated. % -% Used in fmri_data.m class constructor. +% :See also: +% - atlas +% - fmri_data % if 'noverbose' is entered, suppress output verbose = isempty(strmatch('noverbose', varargin(cellfun(@ischar, varargin)))); diff --git a/CanlabCore/@atlas/downsample_parcellation.m b/CanlabCore/@atlas/downsample_parcellation.m index 8e36a9f8..cf92921b 100644 --- a/CanlabCore/@atlas/downsample_parcellation.m +++ b/CanlabCore/@atlas/downsample_parcellation.m @@ -1,28 +1,50 @@ function new_atlas_obj = downsample_parcellation(obj, varargin) -% An atlas_obj has multiple label fields meant to label nested parcellations -% at different levels of granularity. labels is the default which must -% contain unique entries and correspond to the number of unique indices -% in the (voxelwise) parcellation index map, but labels_2 ... labels_5 -% may contain non-unique entries corresponding to coarser parcellations. -% This function remaps indices and probability maps to correspond to one -% of these coarser parcellations. It is a lossy operations and all labels -% of finer parcellation than the selected level will be discarded. +% downsample_parcellation Remap an atlas to a coarser nested parcellation. % -% Input :: -% obj - an atlas_object. Must have at least one of labels_2 ... -% labels_5 defined -% varargin - either a vector of new labels to use, a string or leave empty. -% If you provide a vector, its length should equal number of -% regions in original atlas, with redundant labels for regions -% you want to combine in the downsampled atlas. -% If you provide a string, it should be one of -% {'labels_2', 'labels_3', 'labels_4', 'labels_5'} which will -% then be used as the downsampling vector. -% If you don't provide anything the contents of labels_2 is used -% by default. +% An atlas object has multiple label fields meant to label nested +% parcellations at different levels of granularity. 'labels' is the +% default which must contain unique entries and correspond to the number +% of unique indices in the (voxelwise) parcellation index map, but +% labels_2 ... labels_5 may contain non-unique entries corresponding to +% coarser parcellations. This function remaps indices and probability +% maps to correspond to one of these coarser parcellations. It is a lossy +% operation and all labels of finer parcellation than the selected level +% will be discarded. % -% Output :: -% atlas_obj - a new atlas object, reindexed according to labelfield +% :Usage: +% :: +% +% new_atlas_obj = downsample_parcellation(obj, [labelfield_or_vector], ['concat_label_descriptions']) +% +% :Inputs: +% +% **obj:** +% An atlas-class object. Must have at least one of labels_2 ... +% labels_5 defined. +% +% :Optional Inputs: +% +% **labelfield_or_vector:** +% Either a cell-array vector of new labels (whose length must +% equal the number of regions in the original atlas, with +% redundant labels for regions to be combined), or a string from +% {'labels_2', 'labels_3', 'labels_4', 'labels_5'} naming the +% downsampling level. If omitted, 'labels_2' is used by default. +% +% **'concat_label_descriptions':** +% Concatenate label_descriptions of merged regions (separated by +% semicolons) instead of replacing them. +% +% :Outputs: +% +% **new_atlas_obj:** +% A new atlas-class object, reindexed and relabeled according to +% the requested coarser parcellation. +% +% :See also: +% - select_atlas_subset +% - merge_atlases +% - num_regions fprintf('Downsampling %s parcels\n', obj.atlas_name); diff --git a/CanlabCore/@atlas/get_region_volumes.m b/CanlabCore/@atlas/get_region_volumes.m index 8e89d7ff..5169ae50 100644 --- a/CanlabCore/@atlas/get_region_volumes.m +++ b/CanlabCore/@atlas/get_region_volumes.m @@ -1,9 +1,36 @@ function [vol_in_cubic_mm, voxcount, imtx, parcel_index_values] = get_region_volumes(atlas_obj) -% Get the volume (and raw voxel count) of each region in an atlas object +% get_region_volumes Get the volume and raw voxel count of each region in an atlas object. % -% [vol_in_cubic_mm, voxcount, imtx, parcel_index_values] = get_region_volumes(atlas_obj) +% :Usage: +% :: % -% imtx : indicator matrix, 1/0, for each region +% [vol_in_cubic_mm, voxcount, imtx, parcel_index_values] = get_region_volumes(atlas_obj) +% +% :Inputs: +% +% **atlas_obj:** +% An atlas-class object. +% +% :Outputs: +% +% **vol_in_cubic_mm:** +% Row vector of region volumes in mm^3, computed as the voxel +% count multiplied by the voxel volume in mm^3. +% +% **voxcount:** +% Row vector of voxel counts per region. +% +% **imtx:** +% Voxels-by-regions indicator matrix (1/0) showing membership of +% each voxel in each region. +% +% **parcel_index_values:** +% Column vector of integer index values present in obj.dat (one +% per region column of imtx). +% +% :See also: +% - num_regions +% - condf2indic [n_regions, n_regions_with_data, missing_regions] = num_regions(atlas_obj); diff --git a/CanlabCore/@atlas/get_regions_at_crosshairs.m b/CanlabCore/@atlas/get_regions_at_crosshairs.m index 17b47704..a60a07c8 100644 --- a/CanlabCore/@atlas/get_regions_at_crosshairs.m +++ b/CanlabCore/@atlas/get_regions_at_crosshairs.m @@ -1,4 +1,41 @@ function tbl = get_regions_at_crosshairs(obj, varargin) +% get_regions_at_crosshairs Return atlas labels at the current SPM orthviews crosshair. +% +% Query an atlas object at the location of the current SPM orthviews +% crosshair (as reported by spm_orthviews('pos')) and return a table +% listing the atlas labels (and any additional label fields) that +% overlap that voxel, sorted by probability if probability_maps are +% available. +% +% :Usage: +% :: +% +% tbl = get_regions_at_crosshairs(obj, ['display']) +% +% :Inputs: +% +% **obj:** +% An atlas-class object. +% +% :Optional Inputs: +% +% **'display':** +% If passed, the labels and probabilities are also rendered as a +% caption on the SPM orthviews window. +% +% :Outputs: +% +% **tbl:** +% MATLAB table with columns labels, labels_2, labels_3, labels_4, +% labels_5, label_descriptions, and prob, with one row per region +% overlapping the voxel under the crosshair, sorted in descending +% order of probability. +% +% :See also: +% - spm_orthviews +% - orthviews +% - select_atlas_subset + coords = spm_orthviews('pos')'; xyz = obj.volInfo.xyzlist; xyzmm = (obj.volInfo.mat*[xyz, ones(size(xyz,1),1)]')'; diff --git a/CanlabCore/@atlas/horzcat.m b/CanlabCore/@atlas/horzcat.m index 0009e0df..4e5e4030 100644 --- a/CanlabCore/@atlas/horzcat.m +++ b/CanlabCore/@atlas/horzcat.m @@ -1,5 +1,32 @@ function obj = horzcat(obj, varargin) -% Use merge_atlas to combine atlases, or newatlas = (atlas1, atlas2, atlas3); +% horzcat Implements the horzcat ([a b]) operator on atlas objects. +% +% Combine two or more atlas-class objects by sequentially calling +% merge_atlases on each input. Equivalent to writing +% [atlas1 atlas2 atlas3]. +% +% :Usage: +% :: +% +% obj = horzcat(atlas1, atlas2, atlas3, ...) +% obj = [atlas1 atlas2 atlas3 ...] % equivalent +% +% :Inputs: +% +% **obj:** +% First atlas-class object; defines the space and voxel size. +% +% **varargin:** +% Additional atlas-class objects to merge into obj. +% +% :Outputs: +% +% **obj:** +% Atlas-class object containing the union of regions across +% inputs (combined via merge_atlases). +% +% :See also: +% - merge_atlases for i = 1:length(varargin) diff --git a/CanlabCore/@atlas/isosurface.m b/CanlabCore/@atlas/isosurface.m index 37e4e329..5dbc78be 100644 --- a/CanlabCore/@atlas/isosurface.m +++ b/CanlabCore/@atlas/isosurface.m @@ -1,33 +1,72 @@ function [surface_handles, colors, patch_handles] = isosurface(atlas_obj, varargin) -% Create a series of surfaces in different colors, one for each region -% - Options for single color +% isosurface Create a series of surfaces in different colors, one per atlas region. % -% surface_handles = isosurface(atlas_obj, [optional arguments]) +% Convert the atlas object to a region object and render each region as +% a 3-D isosurface. Supports custom colors and an option to disable +% automatic left/right color matching. % -% optional arguments: -% - Any optional inputs to imageCluster, e.g., 'alpha' -% - 'colors', followed by single color in { } or cell array of multiple colors -% - 'nomatchleftright', do not match colors across hemispheres (left/right) -% Note: The default matches, and may override your colors. +% :Usage: +% :: % -% Examples: -% atlasfile = which('Morel_thalamus_atlas_object.mat'); -% load(atlasfile) +% [surface_handles, colors, patch_handles] = isosurface(atlas_obj, [optional arguments]) % -% surface_handles = isosurface(atlas_obj); -% surface_handles = isosurface(atlas_obj, 'alpha', .5); -% surface_handles = isosurface(atlas_obj, 'alpha', .5, 'nomatchleftright'); +% :Inputs: % -% view(135, 30); -% lightRestoreSingle; -% lightFollowView; +% **atlas_obj:** +% An atlas-class object. % -% load(which('CIT168_MNI_subcortical_atlas_object.mat')); +% :Optional Inputs: % -% surface_handles = isosurface(atlas_obj, 'alpha', .5, 'color', {[.3 .6 .4] [.5 .4 .2]}); -% surface_handles = isosurface(atlas_obj, 'alpha', .5, 'color', {[.3 .6 .4] [.5 .4 .2]}, 'nomatchleftright'); -% p = addbrain('hires right'); -% lightFollowView; +% **Any optional inputs to imageCluster:** +% e.g., 'alpha' followed by a value in [0,1] for surface +% transparency. +% +% **'colors':** +% Followed by a single color in { } or a cell array of multiple +% colors. +% +% **'nomatchleftright':** +% Do not match colors across hemispheres (left/right). The default +% matches L/R and may override user-supplied colors. +% +% :Outputs: +% +% **surface_handles:** +% Vector of patch handles, one per region (legacy form; coerces +% Patch objects to doubles). +% +% **colors:** +% Cell array of [r g b] color triplets used for each region. +% +% **patch_handles:** +% Cell array of patch object handles, one per region. +% +% :Examples: +% :: +% +% atlasfile = which('Morel_thalamus_atlas_object.mat'); +% load(atlasfile) +% +% surface_handles = isosurface(atlas_obj); +% surface_handles = isosurface(atlas_obj, 'alpha', .5); +% surface_handles = isosurface(atlas_obj, 'alpha', .5, 'nomatchleftright'); +% +% view(135, 30); +% lightRestoreSingle; +% lightFollowView; +% +% load(which('CIT168_MNI_subcortical_atlas_object.mat')); +% +% surface_handles = isosurface(atlas_obj, 'alpha', .5, 'color', {[.3 .6 .4] [.5 .4 .2]}); +% surface_handles = isosurface(atlas_obj, 'alpha', .5, 'color', {[.3 .6 .4] [.5 .4 .2]}, 'nomatchleftright'); +% p = addbrain('hires right'); +% lightFollowView; +% +% :See also: +% - atlas2region +% - region/isosurface +% - imageCluster +% - match_colors_left_right r = atlas2region(atlas_obj); diff --git a/CanlabCore/@atlas/label_table.m b/CanlabCore/@atlas/label_table.m index 977d2d35..98f0872a 100644 --- a/CanlabCore/@atlas/label_table.m +++ b/CanlabCore/@atlas/label_table.m @@ -1,30 +1,48 @@ function tbl=label_table(atlas_obj) - % LABEL_TABLE Create a table from an atlas object - % - % TBL = LABEL_TABLE(ATLAS_OBJ) takes an atlas object ATLAS_OBJ and - % creates a table TBL containing the label descriptions and various - % labels. - % - % Input: - % - atlas_obj: An object containing atlas data with the following - % properties: - % * label_descriptions: Descriptions of the labels - % * labels: Primary labels - % * labels_2: Secondary labels - % * labels_3: Tertiary labels - % * labels_4: Quaternary labels - % * labels_5: Quinary labels - % - % Output: - % - tbl: A table containing the label descriptions and labels from - % the atlas object, with appropriate variable names. - % - % Example: - % atlas_obj = load_atlas('canlab2024'); - % tbl = label_table(atlas_obj); - % disp(tbl); - % - % Michael Sun, Ph.D. 06/03/2024 +% label_table Create a table from an atlas object's label fields. +% +% Take an atlas object and produce a MATLAB table whose rows correspond +% to atlas regions and whose columns are label_descriptions, labels, +% labels_2, labels_3, labels_4, and labels_5. Empty auxiliary label +% fields are padded with NaN so that all columns have the same length. +% +% :Usage: +% :: +% +% tbl = label_table(atlas_obj) +% +% :Inputs: +% +% **atlas_obj:** +% An atlas-class object with the following properties: +% +% - label_descriptions: Descriptions of the labels +% - labels: Primary labels +% - labels_2: Secondary labels +% - labels_3: Tertiary labels +% - labels_4: Quaternary labels +% - labels_5: Quinary labels +% +% :Outputs: +% +% **tbl:** +% MATLAB table with columns label_descriptions, labels, labels_2, +% labels_3, labels_4, and labels_5. +% +% :Examples: +% :: +% +% atlas_obj = load_atlas('canlab2024'); +% tbl = label_table(atlas_obj); +% disp(tbl); +% +% :See also: +% - load_atlas +% - select_atlas_subset +% +% .. +% Michael Sun, Ph.D. 06/03/2024 +% .. % Check if the input is valid if ~strcmp(class(atlas_obj), 'atlas') diff --git a/CanlabCore/@atlas/merge_atlases.m b/CanlabCore/@atlas/merge_atlases.m index 7a573908..a8d863f0 100644 --- a/CanlabCore/@atlas/merge_atlases.m +++ b/CanlabCore/@atlas/merge_atlases.m @@ -1,22 +1,57 @@ function atlas_obj = merge_atlases(atlas_obj, atlas_obj_to_add, varargin) -% Add all or some regions from an atlas object to another atlas object (with/without replacing existing labeled voxels) +% merge_atlases Add regions from one atlas object to another, with options to replace voxels. % -% atlas_obj = merge_atlases(atlas_obj, atlas_obj_to_add, [optional arguments]) +% The first input atlas_obj defines the space and voxel size. By default, +% values in atlas_obj_to_add are added to the probability maps, if +% available, and the highest-probability region determines the voxel +% label. If probability maps are missing, the default is to replace +% labels in atlas_obj with the ones from the to-be-added atlas. The +% 'noreplace' option zeroes out values in the to-add atlas where they +% are already in the original atlas, privileging the atlas entered first. % -% - First input atlas_obj defines space and voxel size -% - By default, values in atlas_obj_to_add are added to probability maps, if available, -% and highest-probability region determines voxel label. If probability maps are missing, -% default is to replace labels in atlas_obj with the ones from the -% to-be-added atlas. The 'noreplace' option zeroes out the values in the to-add atlas if they are already -% in the original atlas, privileging the atlas entered first. +% :Usage: +% :: % +% atlas_obj = merge_atlases(atlas_obj, atlas_obj_to_add, [optional arguments]) % -% Optional arguments: -% Inputs to select_atlas_subset, e.g., vector of region numbers or cell array of region names -% e.g., [1 3], {'VPL' 'IFG'} +% :Inputs: % -% 'noreplace' : Do not replace voxels in original atlas; see above. -% 'always_replace' : always replace voxels in original atlas; see above. +% **atlas_obj:** +% Atlas-class object that defines the space and voxel size of +% the result. +% +% **atlas_obj_to_add:** +% Atlas-class object whose regions will be added to atlas_obj. +% It will be resampled to atlas_obj's space if necessary. +% +% :Optional Inputs: +% +% **Inputs to select_atlas_subset:** +% e.g., a vector of region numbers ([1 3]) or a cell array of +% region names ({'VPL' 'IFG'}). Used to subset +% atlas_obj_to_add before merging. +% +% **'noreplace':** +% Do not replace voxels already in the original atlas; the +% original atlas takes precedence. +% +% **'always_replace':** +% Always replace voxels in the original atlas with values from +% the to-be-added atlas. +% +% **'noverbose':** +% Suppress progress messages. +% +% :Outputs: +% +% **atlas_obj:** +% The combined atlas-class object. +% +% :See also: +% - select_atlas_subset +% - probability_maps_to_region_index +% - resample_space +% - horzcat % ------------------------------------------------------------------------- % DEFAULTS AND INPUTS diff --git a/CanlabCore/@atlas/montage.m b/CanlabCore/@atlas/montage.m index 5c8e18ab..85ba1a97 100644 --- a/CanlabCore/@atlas/montage.m +++ b/CanlabCore/@atlas/montage.m @@ -1,91 +1,116 @@ function o2 = montage(obj, varargin) -% This function displays an atlas object on a standard slice montage -% Call with 'nosymmetric' or 'colors' ... to match colors with wedge plots -% +% montage Display an atlas object on a standard slice montage. +% +% Convert the atlas object to a region object and forward to +% region/montage. By default, regions are colored with a standard color +% map; pass 'sourcespace' to override the source-space description used +% for surface projection. +% % :Usage: % :: % -% montage(obj, [optional inputs]) +% montage(obj, [optional inputs]) % -% - takes all optional inputs to canlab_results_fmridisplay and addblobs method +% Takes all optional inputs to canlab_results_fmridisplay and the +% addblobs method. % -% :Input: +% :Inputs: % % **obj:** -% a region object +% An atlas-class object. +% +% :Optional Inputs: % -% :Optional Inputs: - see help canlab_results_fmridisplay +% See help canlab_results_fmridisplay for the full list. Common +% options include: % -% **o2*** -% An existing fmridisplay object, with no keyword strings +% **o2:** +% An existing fmridisplay object, with no keyword strings. % -% **symmetric** [default] -% Mirror left/right blobs with same colors -% See match_colors_left_right +% **'symmetric' [default]:** +% Mirror left/right blobs with same colors. See +% match_colors_left_right. % -% **nosymmetric** -% Standard color map, no L/R color-matching for symmetry -% Call with 'nosymmetric' to match colors with wedge plots +% **'nosymmetric':** +% Standard color map, no L/R color-matching for symmetry. Call +% with 'nosymmetric' to match colors with wedge plots. % % **'noblobs':** -% do not display blobs +% Do not display blobs. % % **'nooutline':** -% do not display blob outlines +% Do not display blob outlines. % % **'addmontages':** -% when entering existing fmridisplay obj, add new montages +% When entering existing fmridisplay obj, add new montages. % % **'noremove':** -% do not remove current blobs when adding new ones +% Do not remove current blobs when adding new ones. % -% **'outlinecolor:** -% followed by new outline color +% **'outlinecolor':** +% Followed by new outline color. % % **'splitcolor':** -% followed by 4-cell new split colormap colors (help fmridisplay or edit code for defaults as example) +% Followed by a 4-cell new split colormap colors (help +% fmridisplay or edit code for defaults as example). % % **'montagetype':** -% 'full' for full montages of axial and sagg slices. -% +% 'full' for full montages of axial and saggital slices. % 'full hcp' for full montage, but with surfaces and volumes from -% HCP data -% -% 'compact' [default] for single-figure parasagittal and axials slices. -% +% HCP data. +% 'compact' [default] for single-figure parasagittal and axial slices. % 'compact2': like 'compact', but fewer axial slices. -% -% 'multirow': followed by number of rows -% e.g., o2 = canlab_results_fmridisplay([], 'multirow', 2); +% 'multirow': followed by number of rows, e.g., +% o2 = canlab_results_fmridisplay([], 'multirow', 2). % % **'noverbose':** -% suppress verbose output, good for scripts/publish to html, etc. +% Suppress verbose output, good for scripts/publish to html, etc. % % **'overlay':** -% specify anatomical image for montage (not surfaces), followed by -% image name -% e.g., o2 = canlab_results_fmridisplay([], 'overlay', 'icbm152_2009_symmetric_for_underlay.img')'; +% Specify anatomical image for montage (not surfaces), followed +% by image name, e.g., o2 = canlab_results_fmridisplay([], ... +% 'overlay', 'icbm152_2009_symmetric_for_underlay.img'). +% The default brain for overlays is based on Keuken et al. 2014. +% For legacy SPM8 single subject, enter as arguments: 'overlay', +% which('SPM8_colin27T1_seg.img'). % -% The default brain for overlays is based on Keuken et al. 2014 -% For legacy SPM8 single subject, enter as arguments: -% 'overlay', which('SPM8_colin27T1_seg.img') +% **'indexmap':** +% Followed by a colormap (n x 3) to use as the indexed colormap +% for atlas regions. Defaults to scn_standard_colors(num_regions). % -% Other inputs to addblobs (fmridisplay method) are allowed, e.g., 'cmaprange', [-2 2], 'trans' +% **'sourcespace':** +% Followed by a source-space identifier used by the surface +% rendering routines (e.g., 'MNI152NLin2009cAsym'). Defaults to +% obj.space_description. % -% See help fmridisplay and region/montage -% e.g., 'color', [1 0 0] +% Other inputs to addblobs (fmridisplay method) are allowed, e.g., +% 'cmaprange', [-2 2], 'trans'. % +% :Outputs: % -% Examples: +% **o2:** +% An fmridisplay object with the atlas regions rendered on slices +% and (optionally) surfaces. % -% o2 = canlab_results_fmridisplay([], 'noverbose'); -% o2 = montage(r, o2); % symmetric colors left/right -% o2 = removeblobs(o2); -% o2 = montage(r, o2, 'map'); - -% PROGRAMMERS' NOTES: -% Tor Wager, Feb 2018. -% This function simply invokes the region montage method. +% :Examples: +% :: +% +% o2 = canlab_results_fmridisplay([], 'noverbose'); +% o2 = montage(r, o2); % symmetric colors left/right +% o2 = removeblobs(o2); +% o2 = montage(r, o2, 'map'); +% +% :See also: +% - region/montage +% - canlab_results_fmridisplay +% - fmridisplay +% - atlas2region +% +% .. +% Programmer Notes: +% Tor Wager, Feb 2018. This function simply invokes the region montage +% method. +% .. cmap = []; diff --git a/CanlabCore/@atlas/num_regions.m b/CanlabCore/@atlas/num_regions.m index 5e31b944..b9945c0e 100644 --- a/CanlabCore/@atlas/num_regions.m +++ b/CanlabCore/@atlas/num_regions.m @@ -1,14 +1,38 @@ function [n_regions, n_regions_with_data, missing_regions] = num_regions(obj) -% Count number of regions in atlas object, even with incomplete data +% num_regions Count number of regions in atlas object, even with incomplete data. % -% [n_regions, n_regions_with_data, missing_regions] = num_regions(obj) +% Returns the total number of regions defined by the atlas (taking the +% maximum across probability_maps columns, labels, and unique values in +% obj.dat), the number of regions that have at least one assigned voxel, +% and the index values of any regions with no assigned voxels. % -% n_regions = number of total regions -% n_regions_with_data = number of regions with some assigned voxels -% missing_regions = index values of regions with no assigned voxels +% Missing regions can cause problems for some atlas functions; see +% atlas.check_properties. % -% missing regions can cause problems for some atlas functions! -% see atlas.check_properties +% :Usage: +% :: +% +% [n_regions, n_regions_with_data, missing_regions] = num_regions(obj) +% +% :Inputs: +% +% **obj:** +% An atlas-class object. +% +% :Outputs: +% +% **n_regions:** +% Total number of regions defined by the atlas. +% +% **n_regions_with_data:** +% Number of regions with at least one assigned voxel. +% +% **missing_regions:** +% Index values of regions with no assigned voxels. +% +% :See also: +% - check_properties +% - get_region_volumes n1 = size(obj.probability_maps, 2); n2 = length(obj.labels); diff --git a/CanlabCore/@atlas/probability_maps_to_region_index.m b/CanlabCore/@atlas/probability_maps_to_region_index.m index e090c364..551d0550 100644 --- a/CanlabCore/@atlas/probability_maps_to_region_index.m +++ b/CanlabCore/@atlas/probability_maps_to_region_index.m @@ -1,12 +1,41 @@ function dat = probability_maps_to_region_index(dat) -% Use dat.probability_maps to rebuild integer vector of index labels (dat.dat) +% probability_maps_to_region_index Rebuild integer index labels from probability_maps. % -% dat = probability_maps_to_region_index(dat) +% Use dat.probability_maps to rebuild the integer vector of index labels +% in dat.dat: each voxel is assigned to the region with maximum +% probability, with voxels having all-zero or all-NaN probabilities set +% to 0. If some regions are missing (no nonzero column in +% probability_maps), they and their corresponding labels and +% label_descriptions are dropped from the atlas, and the remaining index +% values are renumbered consecutively. % -% Note: this script should not invoke remove_empty, replace_empty or +% :Usage: +% :: +% +% dat = probability_maps_to_region_index(dat) +% +% :Inputs: +% +% **dat:** +% An atlas-class object with a populated probability_maps field. +% +% :Outputs: +% +% **dat:** +% Atlas-class object whose .dat is an integer index vector +% consistent with the columns of probability_maps. +% +% :Notes: +% +% This script should not invoke remove_empty, replace_empty, or % resampling because it is invoked on atlas objects that may have -% mismatched probability_maps and dat fields, causing such functions to +% mismatched probability_maps and dat fields, causing such functions to % fail. +% +% :See also: +% - check_properties +% - num_regions +% - select_atlas_subset % Start: dat has one image per region, with probability values % convert to integer vector diff --git a/CanlabCore/@atlas/remove_atlas_region.m b/CanlabCore/@atlas/remove_atlas_region.m index 64fc1f16..4e7218c3 100644 --- a/CanlabCore/@atlas/remove_atlas_region.m +++ b/CanlabCore/@atlas/remove_atlas_region.m @@ -1,9 +1,36 @@ function atlas_obj = remove_atlas_region(atlas_obj, varargin) -% Removes region(s) from atlas, by names or index values -% See select_atlas_subset for input format for region names/values +% remove_atlas_region Remove region(s) from an atlas, by names or index values. % -% atlas_obj = remove_atlas_region(atlas_obj, [region names cell or index values vector]) -% +% Identify regions to remove via select_atlas_subset (passing through any +% region-name cell or integer-index inputs), then remove them from the +% probability_maps, .dat, labels, and references fields of the atlas +% object, renumbering the remaining regions accordingly. +% +% :Usage: +% :: +% +% atlas_obj = remove_atlas_region(atlas_obj, [region names cell or index values vector]) +% +% :Inputs: +% +% **atlas_obj:** +% An atlas-class object. +% +% **varargin:** +% Region selection inputs accepted by select_atlas_subset, e.g., +% a cell array of region name strings or a vector of integer +% index values. +% +% :Outputs: +% +% **atlas_obj:** +% Atlas-class object with the specified regions removed and +% labels/indices renumbered consecutively. +% +% :See also: +% - select_atlas_subset +% - probability_maps_to_region_index +% - check_properties [atlas_obj, has_pmaps] = check_properties(atlas_obj); diff --git a/CanlabCore/@atlas/select_atlas_subset.m b/CanlabCore/@atlas/select_atlas_subset.m index 665d19c5..572ac0b9 100644 --- a/CanlabCore/@atlas/select_atlas_subset.m +++ b/CanlabCore/@atlas/select_atlas_subset.m @@ -1,96 +1,132 @@ function [obj_subset, to_extract] = select_atlas_subset(obj, varargin) -% Select a subset of regions in an atlas by name or integer code, with or without collapsing regions together +% select_atlas_subset Select a subset of regions in an atlas by name or integer code. % -% Note, some atlases are probablistic, and this may result in counterintuitive bheavior. If visualizing -% a probablistic atlas you only see p(region) > p(other region), a winner takes all parcellation scheme where -% a voxel is labeled as belonging to a region only if the probability is greater than the probability of -% it belonging to any other region. However, when you extract such a region you additionally get voxels -% with non-zero probability of belonging to said region even if other region labels are more probable. -% Consequently the extracted region boundaries will EXCEED those of the region you might have seen -% when visualizing the complete atlas, which can be confusing. Intelligent use of the probabilistic -% labels is ideal, but if you prefer a more intuitive (albeit misleading) solution you should use the -% 'deterministic' option' +% Select regions either by passing a cell array of strings to match +% against label fields, by passing a vector of integer region indices, or +% both. Optionally collapse the subset into a single mask region. % -% Select a subset of regions in an atlas using: -% - a cell array of one or more strings to search for in labels -% - a vector of one or more integers indexing regions +% Note: some atlases are probabilistic, which may produce +% counterintuitive behavior. When visualizing a probabilistic atlas, a +% winner-takes-all parcellation is typically displayed (a voxel is +% labeled with a region only if its probability there exceeds the +% probability for any other region). However, when extracting such a +% region you additionally get voxels with non-zero probability of +% belonging to said region even if other region labels are more probable. +% Consequently the extracted region boundaries will EXCEED those of the +% region you might have seen when visualizing the complete atlas, which +% can be confusing. Intelligent use of the probabilistic labels is +% ideal, but if you prefer a more intuitive (albeit misleading) solution +% you should use the 'deterministic' option. % -% Output: -% a new object, obj_subset -% to_extract, a logical vector indicating which regions were selected +% :Usage: +% :: % -% obj_subset = select_atlas_subset(obj, varargin) +% [obj_subset, to_extract] = select_atlas_subset(obj, [optional inputs]) % -% Options: -% 'flatten' : Flatten integer vector in .dat to a single 1/0 mask. Good for -% creating masks from combinations of regions, or adding a set to another -% atlas as a single atlas region. .probability_maps is reestimated as -% posterior probability of union of constituent regions under conditional -% independence assumption. If probability maps don't sum to 1 across voxels -% then conditional independence assumption is violated and we default to -% using the maximum probability instead. +% :Inputs: % -% 'conditionally_ind' : If specified then 'flatten' assumes conditional -% independence even when probabilities don't sum to 1. Does nothing -% otherwise. +% **obj:** +% An atlas-class object. % -% 'labels_2' : If you enter any field name in the object, e.g., labels_2, -% the function will search for keyword matches here instead of in obj.labels. +% :Optional Inputs: % -% 'exact' : If you enter 'exact', function will not look for overlaps in -% names, and only look for exact string matches. +% **strings cell:** +% A cell array of one or more strings to search for in the label +% field (default: obj.labels). % -% 'regexp' : If you enter 'doregexp', function will treat your string as a -% regular expression. +% **integer vector:** +% A vector of one or more integers indexing regions to select. % -% 'deterministic' or 'mostprob' : returns a labeled voxel iff p(region) > argmax(p(other region)). -% Has no effect unless atlas has its probability_maps property populated, -% in which case the default behavior is to return all voxels with -% p(region) > 0. Note: you probably want to apply a threshold operation too -% to remove low probability tissue boundary regions. +% **'flatten':** +% Flatten integer vector in .dat to a single 1/0 mask. Good for +% creating masks from combinations of regions, or adding a set to +% another atlas as a single atlas region. .probability_maps is +% reestimated as the posterior probability of the union of +% constituent regions under a conditional independence assumption. +% If probability maps do not sum to 1 across voxels then the +% conditional independence assumption is violated and the +% function defaults to using the maximum probability instead. % -% Examples: -% -% atlasfile = which('Morel_thalamus_atlas_object.mat'); -% load(atlasfile) -% [obj_subset, to_extract] = select_atlas_subset(atlas_obj, [1 3]); -% [obj_subset, to_extract] = select_atlas_subset(atlas_obj, [1 3], {'VPL'}); -% [obj_subset, to_extract] = select_atlas_subset(atlas_obj, {'VPL'}); % Sensory VPL thalamus, when using morel atlas -% [obj_subset, to_extract] = select_atlas_subset(atlas_obj, {'VPL' 'VPM' 'VPI'}); % Sensory thalamus, both lat and medial +% **'conditionally_ind':** +% If specified, then 'flatten' assumes conditional independence +% even when probabilities do not sum to 1. Does nothing otherwise. % -% [obj_subset, to_extract] = select_atlas_subset(atlas_obj, {'Hb'}); % Habenula +% **field name (e.g., 'labels_2'):** +% If you enter any field name in the object, e.g., labels_2, the +% function will search for keyword matches there instead of in +% obj.labels. % -% [obj_subset, to_extract] = select_atlas_subset(atlas_obj, {'CL' 'CeM' 'CM' 'Pf'}); % Intralaminar -% [obj_subset, to_extract] = select_atlas_subset(atlas_obj, {'Pv' 'SPf'}); % Midline group -% [obj_subset, to_extract] = select_atlas_subset(atlas_obj, {'CL' 'CeM' 'CM' 'Pf' 'Pv' 'SPf'}); % Intralaminar and Midline group -% [obj_subset, to_extract] = select_atlas_subset(atlas_obj, {'MD'}); % Mediodorsal nuc. +% **'exact':** +% Function will not look for overlaps in names; only exact string +% matches will be accepted. % -% [obj_subset, to_extract] = select_atlas_subset(atlas_obj, {'Pu'}); % Pulvinar -% [obj_subset, to_extract] = select_atlas_subset(atlas_obj, {'LGN'}); % LGN -% [obj_subset, to_extract] = select_atlas_subset(atlas_obj, {'MGN'}); % MGN +% **'regexp':** +% Treat your string(s) as regular expressions. % -% Create a mask from a set of regions: -% obj_subset = select_atlas_subset(atlas_obj, {'CL' 'CeM' 'CM' 'Pf'}, 'flatten'); % Intralaminar -% r = atlas2region(obj_subset); -% orthviews(r) +% **'deterministic' or 'mostprob':** +% Returns a labeled voxel iff p(region) > argmax(p(other +% region)). Has no effect unless the atlas has its +% probability_maps property populated, in which case the default +% behavior is to return all voxels with p(region) > 0. Note: you +% probably want to apply a threshold operation as well to remove +% low-probability tissue boundary regions. % -% Load a canonical atlas and select only the default mode, limbic, and brainstem regions -% based on the .labels_2 property -% ------------------------------------------------------- -% atlas_obj = load_atlas('canlab2018_2mm'); -% [obj_subset, to_extract] = select_atlas_subset(atlas_obj, {'Cerebellum', 'Def'}, 'labels_2'); -% montage(obj_subset); +% :Outputs: % -% see also: image_vector.select_voxels_by_value - -% Programmers' notes: -% Created by tor wager -% Edited by tor, 12/2019, to use any field to select labels; and update -% auxiliary label fields too. +% **obj_subset:** +% A new atlas-class object containing only the selected regions. +% +% **to_extract:** +% Logical vector (1 x n_regions) indicating which regions in obj +% were selected. +% +% :Examples: +% :: +% +% atlasfile = which('Morel_thalamus_atlas_object.mat'); +% load(atlasfile) +% [obj_subset, to_extract] = select_atlas_subset(atlas_obj, [1 3]); +% [obj_subset, to_extract] = select_atlas_subset(atlas_obj, [1 3], {'VPL'}); +% [obj_subset, to_extract] = select_atlas_subset(atlas_obj, {'VPL'}); % Sensory VPL thalamus, when using morel atlas +% [obj_subset, to_extract] = select_atlas_subset(atlas_obj, {'VPL' 'VPM' 'VPI'}); % Sensory thalamus, both lat and medial +% +% [obj_subset, to_extract] = select_atlas_subset(atlas_obj, {'Hb'}); % Habenula +% +% [obj_subset, to_extract] = select_atlas_subset(atlas_obj, {'CL' 'CeM' 'CM' 'Pf'}); % Intralaminar +% [obj_subset, to_extract] = select_atlas_subset(atlas_obj, {'Pv' 'SPf'}); % Midline group +% [obj_subset, to_extract] = select_atlas_subset(atlas_obj, {'CL' 'CeM' 'CM' 'Pf' 'Pv' 'SPf'}); % Intralaminar and Midline group +% [obj_subset, to_extract] = select_atlas_subset(atlas_obj, {'MD'}); % Mediodorsal nuc. +% +% [obj_subset, to_extract] = select_atlas_subset(atlas_obj, {'Pu'}); % Pulvinar +% [obj_subset, to_extract] = select_atlas_subset(atlas_obj, {'LGN'}); % LGN +% [obj_subset, to_extract] = select_atlas_subset(atlas_obj, {'MGN'}); % MGN +% +% % Create a mask from a set of regions: +% obj_subset = select_atlas_subset(atlas_obj, {'CL' 'CeM' 'CM' 'Pf'}, 'flatten'); % Intralaminar +% r = atlas2region(obj_subset); +% orthviews(r) +% +% % Load a canonical atlas and select only the default mode, limbic, +% % and brainstem regions based on the .labels_2 property: +% atlas_obj = load_atlas('canlab2018_2mm'); +% [obj_subset, to_extract] = select_atlas_subset(atlas_obj, {'Cerebellum', 'Def'}, 'labels_2'); +% montage(obj_subset); +% +% :See also: +% - image_vector.select_voxels_by_value +% - remove_atlas_region +% - merge_atlases % -% Edited by Michael Sun, 09/04/2023, added an 'exact' flag, so that users -% can select to match string labels exactly and not capture regions with -% overlapping labels e.g., Cblm_Vermis_VI and Cblm_Vermis_VII. +% .. +% Programmers' notes: +% Created by Tor Wager. +% Edited by Tor, 12/2019, to use any field to select labels; and update +% auxiliary label fields too. +% Edited by Michael Sun, 09/04/2023, added an 'exact' flag, so that +% users can select to match string labels exactly and not capture +% regions with overlapping labels (e.g., Cblm_Vermis_VI and +% Cblm_Vermis_VII). +% .. % ------------------------------------------------------------------------- % DEFAULTS AND INPUTS diff --git a/CanlabCore/@atlas/split_atlas_by_hemisphere.m b/CanlabCore/@atlas/split_atlas_by_hemisphere.m index 63c7ea87..4bff6989 100644 --- a/CanlabCore/@atlas/split_atlas_by_hemisphere.m +++ b/CanlabCore/@atlas/split_atlas_by_hemisphere.m @@ -1,20 +1,47 @@ function atlas_out = split_atlas_by_hemisphere(atlas_obj) -% Divide regions that are bilateral into separate left- and right-hemisphere regions +% split_atlas_by_hemisphere Divide bilateral regions into left- and right-hemisphere regions. % -% Take an atlas object whose labeled regions are bilateral and divide each -% region into separate left- and right-hemisphere regions. -% - Uses x-coordinates. x-coords exactly zero will be included in R hem regions +% Take an atlas object whose labeled regions are bilateral and divide +% each region into separate left- and right-hemisphere regions, adding +% _L and _R suffixes to the resulting labels. % -% July 2018, Tor Wager +% Uses x-coordinates: x-coords exactly zero are included in R-hem regions. % -% "split_atlas" method use cases: -% split_atlas_by_hemisphere: We have a defined set of bilateral regions that -% we want to "hard-split" into left and right. Multiple discontiguous regions -% with the same label will be kept together. +% :Usage: +% :: % -% split_atlas_into_contiguous_regions: We want to (1) keep contiguous blobs together -% that may cross the midline, and (2) separate contiguous blobs with the -% same label into separate labeled regions. +% atlas_out = split_atlas_by_hemisphere(atlas_obj) +% +% :Inputs: +% +% **atlas_obj:** +% An atlas-class object whose labeled regions are bilateral. +% +% :Outputs: +% +% **atlas_out:** +% Atlas-class object in which each original region has been split +% into _L and _R variants. +% +% :Notes: +% +% 'split_atlas' method use cases: +% +% - split_atlas_by_hemisphere: We have a defined set of bilateral +% regions that we want to 'hard-split' into left and right. Multiple +% discontiguous regions with the same label will be kept together. +% - split_atlas_into_contiguous_regions: We want to (1) keep contiguous +% blobs together that may cross the midline, and (2) separate +% contiguous blobs with the same label into separate labeled regions. +% +% :See also: +% - split_atlas_into_contiguous_regions +% - atlas_add_L_R_to_labels +% - merge_atlases +% +% .. +% July 2018, Tor Wager +% .. n_regions = num_regions(atlas_obj); diff --git a/CanlabCore/@atlas/split_atlas_into_contiguous_regions.m b/CanlabCore/@atlas/split_atlas_into_contiguous_regions.m index efae60b6..e2108196 100644 --- a/CanlabCore/@atlas/split_atlas_into_contiguous_regions.m +++ b/CanlabCore/@atlas/split_atlas_into_contiguous_regions.m @@ -1,20 +1,50 @@ function atlas_out = split_atlas_into_contiguous_regions(atlas_obj) -% Divide regions with multiple contiguous blobs into separate labeled regions for each blob +% split_atlas_into_contiguous_regions Split labeled regions into separate contiguous blobs. % -% Take an atlas object whose labeled regions contain multiple contiguous blobs and divide each -% contiguous blob into a separate labeled region. -% - Note: This version eliminates probability maps - handling them not implemented yet +% Take an atlas object whose labeled regions contain multiple contiguous +% blobs and divide each contiguous blob into a separate labeled region. +% New labels are formed by appending _L, _R, or _M suffixes (based on +% the modal x-coordinate sign and proportional asymmetry) to the +% original region label. % -% July 2018, Tor Wager +% :Usage: +% :: % -% "split_atlas" method use cases: -% split_atlas_by_hemisphere: We have a defined set of bilateral regions that -% we want to "hard-split" into left and right. Multiple discontiguous regions -% with the same label will be kept together. +% atlas_out = split_atlas_into_contiguous_regions(atlas_obj) % -% split_atlas_into_contiguous_regions: We want to (1) keep contiguous blobs together -% that may cross the midline, and (2) separate contiguous blobs with the -% same label into separate labeled regions. +% :Inputs: +% +% **atlas_obj:** +% An atlas-class object. +% +% :Outputs: +% +% **atlas_out:** +% Atlas-class object with each contiguous blob promoted to its +% own labeled region. +% +% :Notes: +% +% This version eliminates probability maps; handling them is not +% implemented yet. +% +% 'split_atlas' method use cases: +% +% - split_atlas_by_hemisphere: We have a defined set of bilateral +% regions that we want to 'hard-split' into left and right. Multiple +% discontiguous regions with the same label will be kept together. +% - split_atlas_into_contiguous_regions: We want to (1) keep contiguous +% blobs together that may cross the midline, and (2) separate +% contiguous blobs with the same label into separate labeled regions. +% +% :See also: +% - split_atlas_by_hemisphere +% - reparse_continguous +% - region2atlas +% +% .. +% July 2018, Tor Wager +% .. [n_regions, n_regions_with_data, missing_regions] = num_regions(atlas_obj); diff --git a/CanlabCore/@fmri_data/annotate_binary_results_map.m b/CanlabCore/@fmri_data/annotate_binary_results_map.m index bc0e2a69..bc2af5c7 100644 --- a/CanlabCore/@fmri_data/annotate_binary_results_map.m +++ b/CanlabCore/@fmri_data/annotate_binary_results_map.m @@ -45,11 +45,19 @@ % % :Examples: % :: +% ------------------------------------------- +% obj = load_image_set('emotionreg'); t = ttest(obj, .005, 'uncorrected'); +% t.dat = single(t.dat > 3); % binarize t-statistic map +% t = fmri_data(t); % recast +% RESULTS = annotate_binary_results_map(t); % +% ------------------------------------------- +% % ns = load(which('neurosynth_data_obj.mat')); % test_map = get_wh_image(ns.topic_obj_reverseinference, 1); % somatosensory topic % annotate_binary_results_map(test_map) % +% % ------------------------------------------- % :See also: % image_similarity_plot, wedge_plot_by_atlas diff --git a/CanlabCore/@fmri_data/annotate_continuous_neuroimage_maps.m b/CanlabCore/@fmri_data/annotate_continuous_neuroimage_maps.m index cc3c24b9..1e10ada2 100644 --- a/CanlabCore/@fmri_data/annotate_continuous_neuroimage_maps.m +++ b/CanlabCore/@fmri_data/annotate_continuous_neuroimage_maps.m @@ -1,8 +1,70 @@ - - -% there are different procedures for smooth maps, which can be resampled to the -% object, and atlases, which can have very small values and are high-res, -% so better to sample the data images to them. +% annotate_continuous_neuroimage_maps Annotate an fmri_data object against continuous reference maps. +% +% :Usage: +% :: +% +% % This file is currently a script that operates on a variable +% % named `obj` already in the workspace; it is not yet wrapped as +% % a callable method. Run it from a context where `obj` is defined: +% obj = load_image_set('emotionreg'); +% annotate_continuous_neuroimage_maps; +% +% Annotates each image in obj by computing spatial correlations +% with a curated set of continuous-valued reference maps and a few +% Neurosynth feature batteries. The reference batteries currently +% covered are: +% +% - Margulies principal gradient of functional connectivity +% (transmodal vs. unimodal axis). +% - Allen Brain transcriptomic gradients. +% - Hansen Neuromaps PET neurochemical tracer maps. +% - Neurosynth topics (forward and reverse inference) and term maps +% (reverse inference). +% - Yeo / Buckner resting-state network similarity, plotted against +% the Yeo / Schaefer 17-network atlas. +% +% :Inputs: +% +% **obj:** +% fmri_data object in the caller workspace whose images are to +% be annotated. Must be present before this script is run. +% +% :Outputs: +% +% **t:** +% MATLAB table accumulated in the caller workspace +% containing one row per image and one column per annotation +% battery (transmodal gradient values, transcriptomic PCs, +% neurochemical tracer correlations, top Neurosynth +% topics / terms, etc.). +% +% Several figures are also created (Yeo / Buckner network plot, +% wedge plot by Yeo17 networks). +% +% :Subfunctions: +% +% - prep_and_extract_values_continuous_reference_maps resamples a +% reference map (or stack of reference maps) to the test object's +% space, z-scores image-wise, and returns per-image correlations +% with the test object. +% - plot_colored_bars renders a horizontal bar plot with one +% colour per network from a vector of values and labels. +% +% :Notes: +% +% - There are different procedures for smooth maps (which can be +% resampled to the test object) and atlases (which can have very +% small values and are high-resolution, so it is better to resample +% the data images to them). +% - This file does not currently begin with a function declaration +% and therefore behaves as a MATLAB script rather than a callable +% method on the fmri_data class. The doc block above describes the +% intended usage; converting to a proper method is a separate task. +% +% :See also: +% - neurosynth_feature_labels, neurosynth_lexical_plot +% - image_similarity_plot, image_similarity_plot_bucknermaps +% - wedge_plot_by_atlas %% Transmodal vs. unimodal: Principal gradient of functional connectivity diff --git a/CanlabCore/@fmri_data/bootstrap_structure_coeff_diff.m b/CanlabCore/@fmri_data/bootstrap_structure_coeff_diff.m deleted file mode 100644 index 6c19f8f9..00000000 --- a/CanlabCore/@fmri_data/bootstrap_structure_coeff_diff.m +++ /dev/null @@ -1,99 +0,0 @@ -function [r1_obj, r2_obj, rdiff_obj] = bootstrap_structure_coeff_diff(obj, pattern1, pattern2) -% nps = load_image_set('nps'); -% vps = load_image_set('vps'); -% obj = load_image_set('emotionreg'); -% -% pattern1 = nps; pattern2 = vps; -% [r1_obj, r2_obj, rdiff_obj] = bootstrap_structure_coeff_diff(obj, pattern1, pattern2) -% -% % Re-threshold maps and display again: -% r1_obj = threshold(r1_obj, .05, 'unc'); -% rdiff_obj = threshold(rdiff_obj, .05, 'unc'); -% -% THIS FUNCTION IS DEPRECATED - SEE STRUCTURE_COEFFICIENT_MAP() - -error('THIS FUNCTION IS DEPRECATED - SEE STRUCTURE_COEFFICIENT_MAP()') - -obj.dat = double(obj.dat); % superstition - -%% Apply patterns - -pexp1 = double(apply_mask(obj, pattern1, 'pattern_expression', 'cosine_similarity')); - -pexp2 = double(apply_mask(obj, pattern2, 'pattern_expression', 'cosine_similarity')); - -%% Calculate structure coefficient maps for full sample - -r1 = corr(obj.dat', pexp1); - -r2 = corr(obj.dat', pexp2); - -rdiff = r1 - r2; - - -%% Set up bootstrap samples - -nboot = 100; -bootsam = setup_boot_samples(pexp1, nboot); - -%% - -nvox = size(obj.dat, 1); -[r1_boot, r2_boot, rdiff_boot] = deal(NaN .* zeros(nvox, nboot)); - -tic - -for i = 1:nboot - - indx = bootsam(:, i); - - obj_boot = get_wh_image(obj, indx); - - pexp1_boot = pexp1(indx); - pexp2_boot = pexp2(indx); - - % Get summary statistic maps for this bootstrap sample - % voxels x bootstrap samples, each column is a map - r1_boot(:, i) = corr(double(obj_boot.dat'), pexp1_boot); - - r2_boot(:, i) = corr(double(obj_boot.dat'), pexp2_boot); - - rdiff_boot(:, i) = r1_boot(:, i) - r2_boot(:, i); - -end % bootstrap iteration - -toc - -%% - -r1_obj = get_statistic_image(r1_boot, r1, obj, 'Structure coeff map for pattern 1'); - -r2_obj = get_statistic_image(r2_boot, r2, obj, 'Structure coeff map for pattern 2'); - -rdiff_obj = get_statistic_image(rdiff_boot, rdiff, obj, 'Structure coeff map for pattern 1 - 2'); - - -end % main function - - - -%% Subfunctions - -function r1_obj = get_statistic_image(r1_boot, r1, obj, descrip) - -r1_p = bootbca_pval(0, @mean, r1_boot' ,r1', obj.dat'); -r1_p = r1_p'; -% r1_z = r1_z'; - -r1_fdr_thresh = FDR(r1_p, 0.05); -if isempty(r1_fdr_thresh), r1_fdr_thresh = -Inf; end - -r1_obj = statistic_image('dat', r1, ... - 'volInfo', obj.volInfo, ... - 'p', r1_p, ... - 'sig', r1_p < r1_fdr_thresh, ... - 'ste', [], ... - 'dat_descrip', descrip, ... - 'removed_voxels', obj.removed_voxels); - -end diff --git a/CanlabCore/@fmri_data/create.m b/CanlabCore/@fmri_data/create.m index 72c1308e..3c9f1fad 100644 --- a/CanlabCore/@fmri_data/create.m +++ b/CanlabCore/@fmri_data/create.m @@ -1,13 +1,43 @@ function obj = create(obj, varargin) -% Create an object from an empty obj structure, assigning fieldname/value -% pairs as optional arguments. +% create Assemble an fmri_data object from fieldname/value pairs. % % :Usage: % :: % -% [obj = create(obj, varargin) +% obj = create(obj, 'fieldname1', value1, 'fieldname2', value2, ...) +% obj = create(obj, ..., 'noverbose') % -% Used in fmri_data.m class constructor. +% Internal helper used by the fmri_data class constructor and other +% methods that need to populate fields of an existing object from a +% list of name/value pairs. For each name/value pair where the name +% matches a property of obj, the corresponding field is assigned. +% NaN values written to .dat are converted to 0 with a notice (unless +% 'noverbose' is supplied). +% +% :Inputs: +% +% **obj:** +% An existing fmri_data (or subclass) object whose fields will +% be populated. +% +% **varargin:** +% Alternating fieldname / value pairs. Fieldnames not matching +% any property of obj are silently ignored. The string +% 'noverbose' anywhere in varargin suppresses informational +% output (e.g., the NaN-to-zero notice). +% +% :Outputs: +% +% **obj:** +% The input object with the requested fields populated. +% +% :See also: +% - fmri_data (class constructor) +% +% .. +% Programmers' notes: +% Used in fmri_data.m class constructor. +% .. % if 'noverbose' is entered, suppress output verbose = isempty(strmatch('noverbose', varargin(cellfun(@ischar, varargin)))); diff --git a/CanlabCore/@fmri_data/extract_measures_batch.m b/CanlabCore/@fmri_data/extract_measures_batch.m index d879ed75..0036c28a 100644 --- a/CanlabCore/@fmri_data/extract_measures_batch.m +++ b/CanlabCore/@fmri_data/extract_measures_batch.m @@ -1,31 +1,47 @@ function DAT = extract_measures_batch(data_obj) -% Extracts a set of measures relevant for pattern-based and network-based analyses +% extract_measures_batch Aggregate pattern- and network-based measures from an fmri_data object. % -% DAT = extract_measures_batch(data_obj) -% -% This is a method called extract_measures_batch for fmri_objects. -% It Extracts a set of measures relevant for pattern-based and network-based analyses. -% The idea is to aggregate these across studies, and pull relevant measures from the set -% for particular analyses. It returns the following structure: +% :Usage: +% :: % -% Tor Wager, August 2018 +% DAT = extract_measures_batch(data_obj) +% +% Extracts a set of measures relevant for pattern-based and +% network-based analyses from an fmri_data object. The intent is to +% aggregate these consistently across studies so that downstream code +% can pull whatever subset is relevant for a given analysis. +% +% :Inputs: +% +% **data_obj:** +% fmri_data object containing one or more images (e.g., a single +% subject's preprocessed time series, or a group-level set of +% contrast images). +% +% :Outputs: +% +% **DAT:** +% Struct with the fields described below. Tabular fields are +% MATLAB table objects. To inspect column names use +% DAT.(field).Properties.VariableNames; table2array(...) +% is useful for extracting numeric data matrices. +% +% :DAT structure: % -% -% DAT = % % struct with fields: % % extracted_on_date: '05-Sep-2018_02_49? Date information was extracted % image_names: 'wrrest_mb8_r1.nii? Original image names (no paths) -% fullpath: [914119 char] Full path names for all volumes (for provenance) -% mahalanobis: [9145 table] Mahalanobis distances (for weighting/nuisance)and outlier ID logical -% rmssd: [11 struct] Root mean square successive differences and outlier ID logical -% gray_white_csf_table: [9145 table] Global avg gray, white, CSF, and 5 principal components for each; for nuisance -% npsplus: [11 struct] Multivariate pattern responses for CANlab measures (NPS, more) -% kragelemotion: [11 struct] Multivariate pattern responses for Kragel 2015 emotion classification -% kragel18: [11 struct] Multivariate PLS pattern responses for Kragel 2018 Nat Neurosci and subregions -% pain_pdm: [11 struct] Multivariate pattern responses for Geuter et al.?s Prin. Dirs of Medation (10 patterns, and combined) -% PARCELS: [11 struct] Parcellations: Averages for each parcel, and local pattern responses (selected) +% fullpath: [914�119 char] Full path names for all volumes (for provenance) +% mahalanobis: [914�5 table] Mahalanobis distances (for weighting/nuisance)and outlier ID logical +% rmssd: [1�1 struct] Root mean square successive differences and outlier ID logical +% gray_white_csf_table: [914�5 table] Global avg gray, white, CSF, and 5 principal components for each; for nuisance +% npsplus: [1�1 struct] Multivariate pattern responses for CANlab measures (NPS, more) +% kragelemotion: [1�1 struct] Multivariate pattern responses for Kragel 2015 emotion classification +% kragel18: [1�1 struct] Multivariate PLS pattern responses for Kragel 2018 Nat Neurosci and subregions +% pain_pdm: [1�1 struct] Multivariate pattern responses for Geuter et al.?s Prin. Dirs of Medation (10 patterns, and combined) +% PARCELS: [1�1 struct] Parcellations: Averages for each parcel, and local pattern responses (selected) % % Tables: % Some variables are in Matlab table objects, e.g., DAT.mahalanobis. @@ -82,28 +98,28 @@ % % struct with fields: % -% dotproduct: [11 struct] % These are different similarity metrics -% cosine_sim: [11 struct] -% correlation: [11 struct] +% dotproduct: [1�1 struct] % These are different similarity metrics +% cosine_sim: [1�1 struct] +% correlation: [1�1 struct] % % similarity_metric: 'dotproduct' % image_scaling: 'none' -% signaturenames: {114 cell} +% signaturenames: {1�14 cell} % conditionnames: {'C__1'} -% NPS: [9141 table] % Each of these is a table object with a different signature -% NPSpos: [9141 table] -% NPSneg: [9141 table] -% SIIPS: [9141 table] -% PINES: [9141 table] -% Rejection: [9141 table] -% VPS: [9141 table] -% VPS_nooccip: [9141 table] -% GSR: [9141 table] -% Heart: [9141 table] -% FM_Multisens: [9141 table] -% FM_pain: [9141 table] -% Empathic_Dist: [9141 table] -% Empathic_Care: [9141 table] +% NPS: [914�1 table] % Each of these is a table object with a different signature +% NPSpos: [914�1 table] +% NPSneg: [914�1 table] +% SIIPS: [914�1 table] +% PINES: [914�1 table] +% Rejection: [914�1 table] +% VPS: [914�1 table] +% VPS_nooccip: [914�1 table] +% GSR: [914�1 table] +% Heart: [914�1 table] +% FM_Multisens: [914�1 table] +% FM_pain: [914�1 table] +% Empathic_Dist: [914�1 table] +% Empathic_Care: [914�1 table] % % Access them and build a matrix like this: % @@ -124,8 +140,8 @@ % % struct with fields: % -% canlab2018_2mm: [11 struct] % ~500-region atlas composite from multiple published atlases and named ROIs -% yeo17networks: [11 struct] % 16 unique rsfMRI networks, separated into left and right hemispheres +% canlab2018_2mm: [1�1 struct] % ~500-region atlas composite from multiple published atlases and named ROIs +% yeo17networks: [1�1 struct] % 16 unique rsfMRI networks, separated into left and right hemispheres % % DAT.PARCELS.yeo17networks % @@ -133,13 +149,13 @@ % % struct with fields: % -% parcel_obj: [11 atlas] % Original atlas object, with region labels, etc. -% means: [11 struct] % .dat has a time x parcels matrix of mean data from each parcel -% NPS: [11 struct] % Local pattern expression for the NPS in each parcel -% SIIPS: [11 struct] -% PINES: [11 struct] -% VPS: [11 struct] -% Empathic_Care: [11 struct] +% parcel_obj: [1�1 atlas] % Original atlas object, with region labels, etc. +% means: [1�1 struct] % .dat has a time x parcels matrix of mean data from each parcel +% NPS: [1�1 struct] % Local pattern expression for the NPS in each parcel +% SIIPS: [1�1 struct] +% PINES: [1�1 struct] +% VPS: [1�1 struct] +% Empathic_Care: [1�1 struct] % % EXAMPLE % ------------------------------------------------------------------------- diff --git a/CanlabCore/@fmri_data/fitlme_voxelwise.m b/CanlabCore/@fmri_data/fitlme_voxelwise.m index 1b22b002..abc61fb0 100644 --- a/CanlabCore/@fmri_data/fitlme_voxelwise.m +++ b/CanlabCore/@fmri_data/fitlme_voxelwise.m @@ -1,718 +1,712 @@ -function out = fitlme_voxelwise(obj, tbl, spec, varargin) -% Voxelwise linear mixed-effects (LME) model using fitlme. -% -% - Uses obj.dat as brain data: [voxels x observations] -% - Uses tbl as design/covariate table: one row per observation -% - Third argument (spec) can be: -% (a) A fitlme formula string, e.g.: -% 'Y ~ 1 + Run + Condition + (1 + Run | Subject)' -% (b) A role table with variables: -% Name : variable name in tbl -% Role : 'group' | 'fixed' | 'mixed' | 'random_only' -% In that case, the function builds the formula automatically. -% -% - Runs the same mixed model at each voxel -% - Returns beta/t maps as statistic_image objects -% - Supports fixed-effect contrasts (matrix C) like regress.m -% -% :Usage (formula mode): -% :: -% -% out = fitlme_voxelwise(obj, tbl, ... -% 'Y ~ 1 + Run + Condition + (1 + Run | Subject)'); -% -% out = fitlme_voxelwise(obj, tbl, ... -% 'Y ~ 1 + Run + Condition + (1 + Run | Subject)', ... -% .005, 'unc', 'C', C, 'contrast_names', {...}); -% -% :Usage (role-table mode): -% :: -% -% % tbl has columns: Subject, Baseline, Run, C1, C2 -% role_tbl = table( ... -% {'Subject'; 'Baseline'; 'Run'; 'C1'; 'C2'}, ... -% {'group'; 'fixed'; 'mixed'; 'mixed'; 'mixed'}, ... -% 'VariableNames', {'Name','Role'}); -% -% out = fitlme_voxelwise(obj, tbl, role_tbl, ... -% .001, 'unc', 'analysis_name', 'LME_with_roles'); -% -% % This internally builds: -% % Y ~ 1 + Baseline + Run + C1 + C2 + (1 + Run + C1 + C2 | Subject) -% -% :Inputs: -% -% **obj:** -% fmri_data (recommended) or image_vector with: -% obj.dat = [nVox x nObs] -% -% **tbl:** -% MATLAB table with nObs rows. -% Does NOT need a 'Y' column; that is added internally per voxel. -% -% **spec:** -% EITHER: -% - fitlme formula string ('Y ~ ...') -% - OR role table with Name/Role columns -% -% :Optional Inputs (similar spirit to regress.m): -% -% **pthr, 'unc' or 'fdr':** -% Threshold for t-maps / contrast t-maps. -% Example: .005, 'unc' or .05, 'fdr' -% If omitted, default is p = .001, 'uncorrected'. -% -% **'C', C:** -% Contrast matrix over *fixed effects*. -% Size: [nFixed x nContrasts], where nFixed is number of fixed effects -% (including intercept) returned by fitlme. -% -% **'contrast_names', {cellstr}:** -% Names for each contrast (columns of C). -% -% **'analysis_name', string:** -% Name/description stored in out.analysis_name. -% -% **'doparallel', logical:** -% Use parfor over voxels (if Parallel Toolbox available). -% Default = false. -% -% **'noverbose':** -% Suppress verbose text output. -% -% **'residual':** -% Save residual time series per voxel as out.resid (fmri_data). -% -% **'fitmethod', 'REML' or 'ML':** -% Passed to fitlme (default 'REML'). -% -% Unsupported options ('robust', 'AR', 'brainony', 'covdat', etc.) -% will be ignored with a warning. -% -% :Outputs (CANlab-style): -% -% **out.b:** -% statistic_image of fixed-effect betas -% - out.b.dat = [nVox x nFixed] -% - out.b.image_labels = fixed effect names -% -% **out.t:** -% statistic_image of fixed-effect t-values (thresholded) -% -% **out.df:** -% fmri_data: df per voxel (scalar DFE from fitlme, first effect) -% -% **out.sigma:** -% fmri_data: residual std dev per voxel -% -% **out.contrast_images (if C provided):** -% statistic_image of contrast beta values -% -% **out.con_t (if C provided):** -% statistic_image of contrast t-values (thresholded) -% -% **out.resid (optional):** -% fmri_data of residuals [nVox x nObs] (transposed at end) -% -% **out.fixed_names:** -% cellstr of fixed-effect names from fitlme -% -% **out.C, out.contrast_names:** -% contrast matrix and names (if given) -% -% **out.formula, out.input_parameters, out.warnings:** -% metadata about the analysis -% -% Examples: -% ----------------------------------------------- -% % Run on BMRK3 pain dataset, 6 temperatures for each of 33 subjects -% % Random intercept/random slope, random effect of temperature -% -% fmri_data_file = which('bmrk3_6levels_pain_dataset.mat'); -% load(fmri_data_file) -% -% % Create metadata_table with images x variables matrix with names -% t = table(image_obj.additional_info.subject_id, image_obj.additional_info.temperatures, 'VariableNames', {'subject_id', 'temperature'}); -% image_obj.metadata_table = t; -% -% % Run fitlme on every voxel using Wilkinson notation to refer to the table: -% out = fitlme_voxelwise(image_obj, image_obj.metadata_table, 'Y ~ 1 + temperature + (1 + temperature | subject_id)', .005, 'unc'); -% t_obj_temp_fitlme = get_wh_image(out.t, 2); -% -% % Compare t-values for fitlme against two-stage summary statistics approach -% % First, create an object with a linear contrast across temps for each person: -% C = [-5 -3 -1 1 3 5]'; -% C_subj = repmat({C}, 1, 33); -% C_subj_matrix = blkdiag(C_subj{:}); -% contrast_dat = image_obj.dat * C_subj_matrix; -% contrast_obj = image_obj; -% contrast_obj.dat = contrast_dat; -% contrast_obj.removed_images = false(33, 1); -% contrast_obj.dat_descrip = '.dat contains one linear contrast across temperatures for each of 33 subjects'; -% -% % Fit 2SSS approach and get t (one sample t-test) -% t_obj_temp_2sss = ttest(contrast_obj); -% t_obj_temp_2sss = threshold(t_obj_temp_2sss, 0.005, 'unc'); -% create_figure; axis off; montage(t_obj_temp_2sss, 'onerow') -% -% % Compare t-maps in a scatterplot -% figure; plot(t_obj_temp_2sss.dat, t_obj_temp_fitlme.dat, '.'); -% hold on; plot([-10 10], [-10 10], '--', 'Color', 'k'); -% ylabel('t-values after normalization'); xlabel('t-values before normalization'); -% -% or: -% h = image_scatterplot(t_obj_temp_2sss, t_obj_temp_fitlme, 'pvaluebox', 0.001, 'colorpoints'); - -% -% ------------------------------------------------------------------------- -% Basic checks -% ------------------------------------------------------------------------- - -if nargin < 3 || isempty(spec) - error('fitlme_voxelwise:TooFewInputs', ... - 'Usage: out = fitlme_voxelwise(obj, tbl, formula_or_role_tbl, [...])'); -end - -if ~istable(tbl) - error('tbl must be a MATLAB table.'); -end - -if ~isa(obj, 'fmri_data') && ~isa(obj, 'image_vector') - error('obj must be fmri_data or image_vector (with .dat and .volInfo).'); -end - -% obj is an object, so use isprop not isfield: -if ~isprop(obj, 'dat') || isempty(obj.dat) - error('obj.dat is missing or empty.'); -end - -Ymat = double(obj.dat); % [nVox x nObs], use double -[nVox, nObs] = size(Ymat); - -if height(tbl) ~= nObs - error('Number of observations in obj.dat (columns) must match height(tbl).'); -end - -% ------------------------------------------------------------------------- -% Build formula from spec (string OR role-table) -% ------------------------------------------------------------------------- - -if ischar(spec) || isstring(spec) - formula = char(spec); % user supplied formula -elseif istable(spec) - role_tbl = spec; - - % Validate role_tbl - if ~all(ismember({'Name','Role'}, role_tbl.Properties.VariableNames)) - error('Role table must have variables: Name, Role.'); - end - - names = cellstr(role_tbl.Name); - roles = lower(cellstr(role_tbl.Role)); - - % Check names exist in tbl - if ~all(ismember(names, tbl.Properties.VariableNames)) - missing = setdiff(names, tbl.Properties.VariableNames); - error('Variables in role_tbl not found in tbl: %s', strjoin(missing, ', ')); - end - - groupvar = names(strcmp(roles, 'group')); - fixed_only_vars = names(strcmp(roles, 'fixed')); - random_and_fixed_vars = names(strcmp(roles, 'mixed')); - random_only_vars = names(strcmp(roles, 'random_only')); - - if numel(groupvar) ~= 1 - error('Exactly one variable must have Role == ''group'' (grouping factor).'); - end - groupvar = groupvar{1}; - - % Fixed part: 1 + fixed_only + random_and_fixed - fixed_terms = {'1'}; - if ~isempty(fixed_only_vars) - fixed_terms = [fixed_terms, fixed_only_vars(:)']; - end - if ~isempty(random_and_fixed_vars) - fixed_terms = [fixed_terms, random_and_fixed_vars(:)']; - end - fixed_terms = unique(fixed_terms, 'stable'); - fixed_str = strjoin(fixed_terms, ' + '); - - % Random part: (1 + random_slopes | groupvar) - random_slopes = unique([random_and_fixed_vars(:); random_only_vars(:)], 'stable'); - random_terms = [{'1'}; random_slopes]; - random_str = sprintf('(%s | %s)', strjoin(random_terms, ' + '), groupvar); - - formula = sprintf('Y ~ %s + %s', fixed_str, random_str); -else - error('Third input must be a formula string or a Name/Role table.'); -end - -% ------------------------------------------------------------------------- -% Defaults -% ------------------------------------------------------------------------- - -pthr = 0.001; -thresh_type = 'uncorrected'; % pass into threshold; compatible with 'unc' / 'fdr' -do_display = false; -doverbose = true; -doparallel = false; -do_resid = false; -fitmethod = 'REML'; -analysis_name = ''; - -C = []; -contrast_names = {}; - -mywarnings = {}; - -% ------------------------------------------------------------------------- -% Parse varargin (similar flavor to regress.m) -% ------------------------------------------------------------------------- - -i = 1; -while i <= length(varargin) - arg = varargin{i}; - - if isnumeric(arg) && i < length(varargin) && ischar(varargin{i+1}) - % threshold pair: pthr, 'unc' or 'fdr' - pthr = arg; - tstr = lower(varargin{i+1}); - if strcmp(tstr, 'unc') - thresh_type = 'uncorrected'; - elseif strcmp(tstr, 'fdr') - thresh_type = 'fdr'; - else - thresh_type = tstr; % allow passing through - end - i = i + 2; - continue; - end - - if ischar(arg) - switch lower(arg) - - case {'display','display_results'} - do_display = true; - i = i + 1; - - case 'nodisplay' - do_display = false; - i = i + 1; - - case 'noverbose' - doverbose = false; - i = i + 1; - - case 'doparallel' - doparallel = true; - i = i + 1; - - case 'residual' - do_resid = true; - i = i + 1; - - case 'fitmethod' - if i == length(varargin) - error('Missing value after ''fitmethod''.'); - end - fitmethod = varargin{i+1}; - i = i + 2; - - case {'analysis_name'} - if i == length(varargin) - error('Missing value after ''analysis_name''.'); - end - analysis_name = varargin{i+1}; - i = i + 2; - - case {'contrast_names'} - if i == length(varargin) - error('Missing value after ''contrast_names''.'); - end - contrast_names = varargin{i+1}; - i = i + 2; - - case {'c','contrasts'} - if i == length(varargin) - error('Missing value after ''C'' / ''contrasts''.'); - end - C = varargin{i+1}; - i = i + 2; - - % --- Unsupported regress options: warn & ignore --- - case {'robust','brainony','brain_is_predictor','grandmeanscale','covdat','ar'} - mywarnings{end+1} = sprintf( ... - 'Option ''%s'' is not implemented for fitlme_voxelwise and will be ignored.', arg); - i = i + 1; - - otherwise - warning('Unknown option "%s" ignored.', arg); - i = i + 1; - end - - else - warning('Unrecognized input at position %d; ignoring.', i); - i = i + 1; - end -end - -if doverbose - fprintf('fitlme_voxelwise: pthr = %.4g, type = %s\n', pthr, thresh_type); - fprintf(' doparallel = %d, fitmethod = %s\n', doparallel, fitmethod); - fprintf(' formula: %s\n', formula); -end - -% ------------------------------------------------------------------------- -% Initial model: get fixed-effect structure from first voxel -% ------------------------------------------------------------------------- - -tbl_work = tbl; -tbl_work.Y = Ymat(1, :)'; - -try - lme0 = fitlme(tbl_work, formula, 'FitMethod', fitmethod); -catch ME - error('Initial fitlme failed at first voxel: %s', ME.message); -end - -[beta0, ~, stats0] = fixedEffects(lme0, 'DFMethod', 'Residual'); %#ok -fixed_names = lme0.CoefficientNames(:)'; % includes intercept if present -nFixed = numel(beta0); - -if doverbose - fprintf('Fixed effects (%d):\n', nFixed); - disp(fixed_names); -end - -% ------------------------------------------------------------------------- -% Validate contrasts C -% ------------------------------------------------------------------------- - -if ~isempty(C) - [kC, nC] = size(C); - if kC ~= nFixed - error('Contrast matrix C must have size [nFixed x nContrasts], here %d x nC, but nFixed = %d.', kC, nFixed); - end - - if ~isempty(contrast_names) - if numel(contrast_names) ~= nC - warning('Length of contrast_names does not match number of contrasts; trimming or padding.'); - contrast_names = contrast_names(:)'; - if numel(contrast_names) < nC - for ii = numel(contrast_names)+1:nC - contrast_names{ii} = sprintf('Con%d', ii); - end - else - contrast_names = contrast_names(1:nC); - end - end - else - contrast_names = arrayfun(@(ii) sprintf('Con%d', ii), 1:nC, 'UniformOutput', false); - end -else - nC = 0; -end - -% ------------------------------------------------------------------------- -% Preallocate voxelwise containers -% ------------------------------------------------------------------------- - -betas = NaN(nFixed, nVox); -tvals = NaN(nFixed, nVox); -pvals = NaN(nFixed, nVox); -SE = NaN(nFixed, nVox); -DFmat = NaN(nFixed, nVox); -sigma = NaN(1, nVox); -mask = false(nVox, 1); - -if do_resid - R = NaN(nObs, nVox); % residual matrix [obs x vox] -end - -if nC > 0 - con_vals = NaN(nC, nVox); - con_t = NaN(nC, nVox); - con_p = NaN(nC, nVox); - con_se = NaN(nC, nVox); - con_df = NaN(nC, nVox); -else - % keep 0 x nVox so indexing works even if never used - con_vals = NaN(0, nVox); - con_t = NaN(0, nVox); - con_p = NaN(0, nVox); - con_se = NaN(0, nVox); - con_df = NaN(0, nVox); -end - -% ------------------------------------------------------------------------- -% Main voxelwise loop -% ------------------------------------------------------------------------- - -if doverbose - fprintf('Running voxelwise LME: %d voxels, %d observations.\n', nVox, nObs); - tic -end - -if doparallel - if doverbose, fprintf('Using PARFOR over voxels.\n'); end - - parfor v = 1:nVox - [betas(:,v), tvals(:,v), pvals(:,v), SE(:,v), DFmat(:,v), ... - sigma(1,v), res_v, ... - con_vals(:,v), con_t(:,v), con_p(:,v), con_se(:,v), con_df(:,v), mask(v)] ... - = fit_lme_one_voxel( ... - Ymat(v,:)', tbl, formula, fitmethod, C, nC, nFixed); - if do_resid - R(:,v) = res_v; - end - end -else - if doverbose - fprintf(' Serial run of %3.0f voxels: %3.0f%%', nVox, 0) - end - - for v = 1:nVox - - if doverbose && rem(v, round(nVox/100)) == 0 - fprintf('\b\b\b\b%3.0f%%', round(100*v/nVox)); - end - - [betas(:,v), tvals(:,v), pvals(:,v), SE(:,v), DFmat(:,v), ... - sigma(1,v), res_v, ... - con_vals(:,v), con_t(:,v), con_p(:,v), con_se(:,v), con_df(:,v), ... - mask(v)] = fit_lme_one_voxel( ... - Ymat(v,:)', tbl, formula, fitmethod, C, nC, nFixed); - if do_resid - R(:,v) = res_v; - end - end -end - -if doverbose - fprintf('Voxelwise LME finished. Successful fits: %d / %d voxels.\n', sum(mask), nVox); - toc -end - -% ------------------------------------------------------------------------- -% Build CANlab-style outputs -% ------------------------------------------------------------------------- - -out = struct; -out.analysis_name = analysis_name; -out.formula = formula; -out.fixed_names = fixed_names; -out.C = C; -out.contrast_names = contrast_names; -out.mask = mask; -out.input_parameters = struct( ... - 'pthr', pthr, 'thresh_type', thresh_type, ... - 'doparallel', doparallel, 'fitmethod', fitmethod); -out.warnings = mywarnings; - -% --- df image (take first fixed-effect df as representative per voxel) --- -df_scalar = DFmat(1, :); % [1 x nVox] -df_img = obj; -df_img.dat = df_scalar'; -df_img.dat_descrip = 'Degrees of freedom (first fixed effect DFE)'; -out.df = df_img; - -% --- sigma image --- -sig_img = obj; -sig_img.dat = sigma'; -sig_img.dat_descrip = 'Residual std dev from LME per voxel'; -out.sigma = sig_img; - -% --- Beta maps as statistic_image --- -b_img = statistic_image; -b_img.type = 'Beta (LME)'; -b_img.dat = betas'; % [nVox x nFixed] -b_img.p = pvals'; % [nVox x nFixed] -b_img.ste = SE'; % [nVox x nFixed] -b_img.N = nObs; -b_img.volInfo = obj.volInfo; -b_img.removed_voxels = obj.removed_voxels; -b_img.removed_images = false; -b_img.image_labels = fixed_names; -b_img.dat_descrip = 'Fixed-effect betas from voxelwise LME'; -b_img = enforce_variable_types(b_img); -out.b = b_img; - -% --- T maps as statistic_image --- -t_img = statistic_image; -t_img.type = 'T (LME fixed effects)'; -t_img.dat = tvals'; % [nVox x nFixed] -t_img.p = pvals'; % [nVox x nFixed] -t_img.ste = SE'; % [nVox x nFixed] -t_img.N = nObs; -t_img.volInfo = obj.volInfo; -t_img.removed_voxels = obj.removed_voxels; -t_img.removed_images = false; -t_img.image_labels = fixed_names; -t_img.dat_descrip = 'Fixed-effect t-values from voxelwise LME'; -t_img = enforce_variable_types(t_img); - -if doverbose - fprintf('Thresholding fixed-effect t-maps at p = %.4g (%s)\n', pthr, thresh_type); -end -t_img = threshold(t_img, pthr, thresh_type); -out.t = t_img; - -% --- Residuals as fmri_data (optional) --- -if do_resid - resid_img = obj; - resid_img.dat = R'; % [nVox x nObs] - resid_img.dat_descrip = 'Residuals from voxelwise LME'; - resid_img.history{end+1} = 'Residuals from fitlme_voxelwise'; - out.resid = resid_img; -end - -% --- Contrasts (if any) --- -if nC > 0 - % Contrast beta - con_img = statistic_image; - con_img.type = 'Contrast (LME fixed effects)'; - con_img.dat = con_vals'; % [nVox x nC] - con_img.p = con_p'; % [nVox x nC] - con_img.ste = con_se'; % [nVox x nC] - con_img.N = nObs; - con_img.volInfo = obj.volInfo; - con_img.removed_voxels = obj.removed_voxels; - con_img.removed_images = false; - con_img.image_labels = contrast_names; - con_img.dat_descrip = 'Contrast beta values from voxelwise LME'; - con_img = enforce_variable_types(con_img); - out.contrast_images = con_img; - - % Contrast t - con_t_img = statistic_image; - con_t_img.type = 'T (LME contrasts)'; - con_t_img.dat = con_t'; % [nVox x nC] - con_t_img.p = con_p'; % [nVox x nC] - con_t_img.ste = con_se'; % [nVox x nC] - con_t_img.N = nObs; - con_t_img.volInfo = obj.volInfo; - con_t_img.removed_voxels = obj.removed_voxels; - con_t_img.removed_images = false; - con_t_img.image_labels = contrast_names; - con_t_img.dat_descrip = 'Contrast t-values from voxelwise LME'; - con_t_img = enforce_variable_types(con_t_img); - - if doverbose - fprintf('Thresholding contrast t-maps at p = %.4g (%s)\n', pthr, thresh_type); - end - con_t_img = threshold(con_t_img, pthr, thresh_type); - out.con_t = con_t_img; -end - -% ------------------------------------------------------------------------- -% Display (optional) -% ------------------------------------------------------------------------- -if do_display - try - orthviews(out.t); - catch - warning('orthviews failed; skipping display.'); - end -end - -end % main function - - -% ===================================================================== -% Helper: fit one voxel's LME and (optionally) contrasts -% ===================================================================== -function [b, t, p, se, df, sigma, res_v, ... - con_vals, con_t, con_p, con_se, con_df, ok] = ... - fit_lme_one_voxel(Yvec, tbl, formula, fitmethod, C, nC, nFixed) - -% Initialize with correct sizes so assignment in parfor works -b = NaN(nFixed, 1); -t = NaN(nFixed, 1); -p = NaN(nFixed, 1); -se = NaN(nFixed, 1); -df = NaN(nFixed, 1); -sigma = NaN; -res_v = NaN(height(tbl), 1); -ok = false; - -if nC > 0 - con_vals = NaN(nC,1); - con_t = NaN(nC,1); - con_p = NaN(nC,1); - con_se = NaN(nC,1); - con_df = NaN(nC,1); -else - con_vals = NaN(0,1); - con_t = NaN(0,1); - con_p = NaN(0,1); - con_se = NaN(0,1); - con_df = NaN(0,1); -end - -% All-NaN Y -> skip (returns NaNs with correct sizes) -if all(isnan(Yvec)) - return; -end - -tbl_local = tbl; -tbl_local.Y = Yvec; - -try - lme = fitlme(tbl_local, formula, 'FitMethod', fitmethod); - - [beta_hat, CovB, stats] = fixedEffects(lme, 'DFMethod', 'Residual'); - - % overwrite initialized NaNs with real values - k = numel(beta_hat); - b(1:k) = beta_hat; - t(1:k) = stats.tStat(:); - p(1:k) = stats.pValue(:); - se(1:k) = stats.SE(:); - - % DFE may be scalar or per-parameter; handle both - if isscalar(stats.DFE) - df(:) = stats.DFE; - else - df(1:k) = stats.DFE(:); - end - - res_v = residuals(lme, 'ResidualType', 'Raw'); - sigma = std(res_v); - - % Contrasts (over fixed effects) - if nC > 0 - for ci = 1:nC - Li = C(:,ci); % [nFixed x 1] - cval = Li' * beta_hat; % scalar - cse = sqrt( Li' * CovB * Li ); - if cse == 0 || isnan(cse) - ct = NaN; - cp = NaN; - else - ct = cval / cse; - dfe_con = stats.DFE; % usually scalar - if isscalar(dfe_con) - cp = 2 * (1 - tcdf(abs(ct), dfe_con)); - else - dfe_con = dfe_con(1); - cp = 2 * (1 - tcdf(abs(ct), dfe_con)); - end - end - con_vals(ci,1) = cval; - con_se(ci,1) = cse; - con_t(ci,1) = ct; - con_p(ci,1) = cp; - if isscalar(stats.DFE) - con_df(ci,1) = stats.DFE; - else - con_df(ci,1) = stats.DFE(1); - end - end - end - - ok = true; -catch - ok = false; -end - -end +function out = fitlme_voxelwise(obj, tbl, spec, varargin) +% fitlme_voxelwise Voxelwise linear mixed-effects (LME) model via MATLAB's fitlme. +% +% :Usage: +% :: +% +% % Formula mode +% out = fitlme_voxelwise(obj, tbl, ... +% 'Y ~ 1 + Run + Condition + (1 + Run | Subject)'); +% +% out = fitlme_voxelwise(obj, tbl, ... +% 'Y ~ 1 + Run + Condition + (1 + Run | Subject)', ... +% .005, 'unc', 'C', C, 'contrast_names', {...}); +% +% % Role-table mode +% % tbl has columns: Subject, Baseline, Run, C1, C2 +% role_tbl = table( ... +% {'Subject'; 'Baseline'; 'Run'; 'C1'; 'C2'}, ... +% {'group'; 'fixed'; 'mixed'; 'mixed'; 'mixed'}, ... +% 'VariableNames', {'Name','Role'}); +% +% out = fitlme_voxelwise(obj, tbl, role_tbl, ... +% .001, 'unc', 'analysis_name', 'LME_with_roles'); +% +% % Role-table mode internally builds the formula: +% % Y ~ 1 + Baseline + Run + C1 + C2 + (1 + Run + C1 + C2 | Subject) +% +% Runs the same linear mixed-effects model at every voxel using +% MATLAB's fitlme. Brain data come from obj.dat (voxels x +% observations); design / covariates come from tbl (one row per +% observation). The third argument may either be an explicit formula +% string or a "role table" from which the function builds a formula +% automatically. Returns beta / t maps as statistic_image objects +% and supports fixed-effect contrasts (matrix C) in the spirit of +% regress.m. +% +% :Inputs: +% +% **obj:** +% fmri_data (recommended) or image_vector with: +% obj.dat = [nVox x nObs] +% +% **tbl:** +% MATLAB table with nObs rows. +% Does NOT need a 'Y' column; that is added internally per voxel. +% +% **spec:** +% EITHER: +% - fitlme formula string ('Y ~ ...') +% - OR role table with Name/Role columns +% +% :Optional Inputs (similar spirit to regress.m): +% +% **pthr, 'unc' or 'fdr':** +% Threshold for t-maps / contrast t-maps. +% Example: .005, 'unc' or .05, 'fdr' +% If omitted, default is p = .001, 'uncorrected'. +% +% **'C', C:** +% Contrast matrix over *fixed effects*. +% Size: [nFixed x nContrasts], where nFixed is number of fixed effects +% (including intercept) returned by fitlme. +% +% **'contrast_names', {cellstr}:** +% Names for each contrast (columns of C). +% +% **'analysis_name', string:** +% Name/description stored in out.analysis_name. +% +% **'doparallel', logical:** +% Use parfor over voxels (if Parallel Toolbox available). +% Default = false. +% +% **'noverbose':** +% Suppress verbose text output. +% +% **'residual':** +% Save residual time series per voxel as out.resid (fmri_data). +% +% **'fitmethod', 'REML' or 'ML':** +% Passed to fitlme (default 'REML'). +% +% Unsupported options ('robust', 'AR', 'brainony', 'covdat', etc.) +% will be ignored with a warning. +% +% :Outputs (CANlab-style): +% +% **out.b:** +% statistic_image of fixed-effect betas +% - out.b.dat = [nVox x nFixed] +% - out.b.image_labels = fixed effect names +% +% **out.t:** +% statistic_image of fixed-effect t-values (thresholded) +% +% **out.df:** +% fmri_data: df per voxel (scalar DFE from fitlme, first effect) +% +% **out.sigma:** +% fmri_data: residual std dev per voxel +% +% **out.contrast_images (if C provided):** +% statistic_image of contrast beta values +% +% **out.con_t (if C provided):** +% statistic_image of contrast t-values (thresholded) +% +% **out.resid (optional):** +% fmri_data of residuals [nVox x nObs] (transposed at end) +% +% **out.fixed_names:** +% cellstr of fixed-effect names from fitlme +% +% **out.C, out.contrast_names:** +% contrast matrix and names (if given) +% +% **out.formula, out.input_parameters, out.warnings:** +% metadata about the analysis +% +% Examples: +% ----------------------------------------------- +% % Run on BMRK3 pain dataset, 6 temperatures for each of 33 subjects +% % Random intercept/random slope, random effect of temperature +% +% fmri_data_file = which('bmrk3_6levels_pain_dataset.mat'); +% load(fmri_data_file) +% +% % Create metadata_table with images x variables matrix with names +% t = table(image_obj.additional_info.subject_id, image_obj.additional_info.temperatures, 'VariableNames', {'subject_id', 'temperature'}); +% image_obj.metadata_table = t; +% +% % Run fitlme on every voxel using Wilkinson notation to refer to the table: +% out = fitlme_voxelwise(image_obj, image_obj.metadata_table, 'Y ~ 1 + temperature + (1 + temperature | subject_id)', .005, 'unc'); +% t_obj_temp_fitlme = get_wh_image(out.t, 2); +% +% % Compare t-values for fitlme against two-stage summary statistics approach +% % First, create an object with a linear contrast across temps for each person: +% C = [-5 -3 -1 1 3 5]'; +% C_subj = repmat({C}, 1, 33); +% C_subj_matrix = blkdiag(C_subj{:}); +% contrast_dat = image_obj.dat * C_subj_matrix; +% contrast_obj = image_obj; +% contrast_obj.dat = contrast_dat; +% contrast_obj.removed_images = false(33, 1); +% contrast_obj.dat_descrip = '.dat contains one linear contrast across temperatures for each of 33 subjects'; +% +% % Fit 2SSS approach and get t (one sample t-test) +% t_obj_temp_2sss = ttest(contrast_obj); +% t_obj_temp_2sss = threshold(t_obj_temp_2sss, 0.005, 'unc'); +% create_figure; axis off; montage(t_obj_temp_2sss, 'onerow') +% +% % Compare t-maps in a scatterplot +% figure; plot(t_obj_temp_2sss.dat, t_obj_temp_fitlme.dat, '.'); +% hold on; plot([-10 10], [-10 10], '--', 'Color', 'k'); +% ylabel('t-values after normalization'); xlabel('t-values before normalization'); +% +% or: +% h = image_scatterplot(t_obj_temp_2sss, t_obj_temp_fitlme, 'pvaluebox', 0.001, 'colorpoints'); + +% +% ------------------------------------------------------------------------- +% Basic checks +% ------------------------------------------------------------------------- + +if nargin < 3 || isempty(spec) + error('fitlme_voxelwise:TooFewInputs', ... + 'Usage: out = fitlme_voxelwise(obj, tbl, formula_or_role_tbl, [...])'); +end + +if ~istable(tbl) + error('tbl must be a MATLAB table.'); +end + +if ~isa(obj, 'fmri_data') && ~isa(obj, 'image_vector') + error('obj must be fmri_data or image_vector (with .dat and .volInfo).'); +end + +% obj is an object, so use isprop not isfield: +if ~isprop(obj, 'dat') || isempty(obj.dat) + error('obj.dat is missing or empty.'); +end + +Ymat = double(obj.dat); % [nVox x nObs], use double +[nVox, nObs] = size(Ymat); + +if height(tbl) ~= nObs + error('Number of observations in obj.dat (columns) must match height(tbl).'); +end + +% ------------------------------------------------------------------------- +% Build formula from spec (string OR role-table) +% ------------------------------------------------------------------------- + +if ischar(spec) || isstring(spec) + formula = char(spec); % user supplied formula +elseif istable(spec) + role_tbl = spec; + + % Validate role_tbl + if ~all(ismember({'Name','Role'}, role_tbl.Properties.VariableNames)) + error('Role table must have variables: Name, Role.'); + end + + names = cellstr(role_tbl.Name); + roles = lower(cellstr(role_tbl.Role)); + + % Check names exist in tbl + if ~all(ismember(names, tbl.Properties.VariableNames)) + missing = setdiff(names, tbl.Properties.VariableNames); + error('Variables in role_tbl not found in tbl: %s', strjoin(missing, ', ')); + end + + groupvar = names(strcmp(roles, 'group')); + fixed_only_vars = names(strcmp(roles, 'fixed')); + random_and_fixed_vars = names(strcmp(roles, 'mixed')); + random_only_vars = names(strcmp(roles, 'random_only')); + + if numel(groupvar) ~= 1 + error('Exactly one variable must have Role == ''group'' (grouping factor).'); + end + groupvar = groupvar{1}; + + % Fixed part: 1 + fixed_only + random_and_fixed + fixed_terms = {'1'}; + if ~isempty(fixed_only_vars) + fixed_terms = [fixed_terms, fixed_only_vars(:)']; + end + if ~isempty(random_and_fixed_vars) + fixed_terms = [fixed_terms, random_and_fixed_vars(:)']; + end + fixed_terms = unique(fixed_terms, 'stable'); + fixed_str = strjoin(fixed_terms, ' + '); + + % Random part: (1 + random_slopes | groupvar) + random_slopes = unique([random_and_fixed_vars(:); random_only_vars(:)], 'stable'); + random_terms = [{'1'}; random_slopes]; + random_str = sprintf('(%s | %s)', strjoin(random_terms, ' + '), groupvar); + + formula = sprintf('Y ~ %s + %s', fixed_str, random_str); +else + error('Third input must be a formula string or a Name/Role table.'); +end + +% ------------------------------------------------------------------------- +% Defaults +% ------------------------------------------------------------------------- + +pthr = 0.001; +thresh_type = 'uncorrected'; % pass into threshold; compatible with 'unc' / 'fdr' +do_display = false; +doverbose = true; +doparallel = false; +do_resid = false; +fitmethod = 'REML'; +analysis_name = ''; + +C = []; +contrast_names = {}; + +mywarnings = {}; + +% ------------------------------------------------------------------------- +% Parse varargin (similar flavor to regress.m) +% ------------------------------------------------------------------------- + +i = 1; +while i <= length(varargin) + arg = varargin{i}; + + if isnumeric(arg) && i < length(varargin) && ischar(varargin{i+1}) + % threshold pair: pthr, 'unc' or 'fdr' + pthr = arg; + tstr = lower(varargin{i+1}); + if strcmp(tstr, 'unc') + thresh_type = 'uncorrected'; + elseif strcmp(tstr, 'fdr') + thresh_type = 'fdr'; + else + thresh_type = tstr; % allow passing through + end + i = i + 2; + continue; + end + + if ischar(arg) + switch lower(arg) + + case {'display','display_results'} + do_display = true; + i = i + 1; + + case 'nodisplay' + do_display = false; + i = i + 1; + + case 'noverbose' + doverbose = false; + i = i + 1; + + case 'doparallel' + doparallel = true; + i = i + 1; + + case 'residual' + do_resid = true; + i = i + 1; + + case 'fitmethod' + if i == length(varargin) + error('Missing value after ''fitmethod''.'); + end + fitmethod = varargin{i+1}; + i = i + 2; + + case {'analysis_name'} + if i == length(varargin) + error('Missing value after ''analysis_name''.'); + end + analysis_name = varargin{i+1}; + i = i + 2; + + case {'contrast_names'} + if i == length(varargin) + error('Missing value after ''contrast_names''.'); + end + contrast_names = varargin{i+1}; + i = i + 2; + + case {'c','contrasts'} + if i == length(varargin) + error('Missing value after ''C'' / ''contrasts''.'); + end + C = varargin{i+1}; + i = i + 2; + + % --- Unsupported regress options: warn & ignore --- + case {'robust','brainony','brain_is_predictor','grandmeanscale','covdat','ar'} + mywarnings{end+1} = sprintf( ... + 'Option ''%s'' is not implemented for fitlme_voxelwise and will be ignored.', arg); + i = i + 1; + + otherwise + warning('Unknown option "%s" ignored.', arg); + i = i + 1; + end + + else + warning('Unrecognized input at position %d; ignoring.', i); + i = i + 1; + end +end + +if doverbose + fprintf('fitlme_voxelwise: pthr = %.4g, type = %s\n', pthr, thresh_type); + fprintf(' doparallel = %d, fitmethod = %s\n', doparallel, fitmethod); + fprintf(' formula: %s\n', formula); +end + +% ------------------------------------------------------------------------- +% Initial model: get fixed-effect structure from first voxel +% ------------------------------------------------------------------------- + +tbl_work = tbl; +tbl_work.Y = Ymat(1, :)'; + +try + lme0 = fitlme(tbl_work, formula, 'FitMethod', fitmethod); +catch ME + error('Initial fitlme failed at first voxel: %s', ME.message); +end + +[beta0, ~, stats0] = fixedEffects(lme0, 'DFMethod', 'Residual'); %#ok +fixed_names = lme0.CoefficientNames(:)'; % includes intercept if present +nFixed = numel(beta0); + +if doverbose + fprintf('Fixed effects (%d):\n', nFixed); + disp(fixed_names); +end + +% ------------------------------------------------------------------------- +% Validate contrasts C +% ------------------------------------------------------------------------- + +if ~isempty(C) + [kC, nC] = size(C); + if kC ~= nFixed + error('Contrast matrix C must have size [nFixed x nContrasts], here %d x nC, but nFixed = %d.', kC, nFixed); + end + + if ~isempty(contrast_names) + if numel(contrast_names) ~= nC + warning('Length of contrast_names does not match number of contrasts; trimming or padding.'); + contrast_names = contrast_names(:)'; + if numel(contrast_names) < nC + for ii = numel(contrast_names)+1:nC + contrast_names{ii} = sprintf('Con%d', ii); + end + else + contrast_names = contrast_names(1:nC); + end + end + else + contrast_names = arrayfun(@(ii) sprintf('Con%d', ii), 1:nC, 'UniformOutput', false); + end +else + nC = 0; +end + +% ------------------------------------------------------------------------- +% Preallocate voxelwise containers +% ------------------------------------------------------------------------- + +betas = NaN(nFixed, nVox); +tvals = NaN(nFixed, nVox); +pvals = NaN(nFixed, nVox); +SE = NaN(nFixed, nVox); +DFmat = NaN(nFixed, nVox); +sigma = NaN(1, nVox); +mask = false(nVox, 1); + +if do_resid + R = NaN(nObs, nVox); % residual matrix [obs x vox] +end + +if nC > 0 + con_vals = NaN(nC, nVox); + con_t = NaN(nC, nVox); + con_p = NaN(nC, nVox); + con_se = NaN(nC, nVox); + con_df = NaN(nC, nVox); +else + % keep 0 x nVox so indexing works even if never used + con_vals = NaN(0, nVox); + con_t = NaN(0, nVox); + con_p = NaN(0, nVox); + con_se = NaN(0, nVox); + con_df = NaN(0, nVox); +end + +% ------------------------------------------------------------------------- +% Main voxelwise loop +% ------------------------------------------------------------------------- + +if doverbose + fprintf('Running voxelwise LME: %d voxels, %d observations.\n', nVox, nObs); + tic +end + +if doparallel + if doverbose, fprintf('Using PARFOR over voxels.\n'); end + + parfor v = 1:nVox + [betas(:,v), tvals(:,v), pvals(:,v), SE(:,v), DFmat(:,v), ... + sigma(1,v), res_v, ... + con_vals(:,v), con_t(:,v), con_p(:,v), con_se(:,v), con_df(:,v), mask(v)] ... + = fit_lme_one_voxel( ... + Ymat(v,:)', tbl, formula, fitmethod, C, nC, nFixed); + if do_resid + R(:,v) = res_v; + end + end +else + if doverbose + fprintf(' Serial run of %3.0f voxels: %3.0f%%', nVox, 0) + end + + for v = 1:nVox + + if doverbose && rem(v, round(nVox/100)) == 0 + fprintf('\b\b\b\b%3.0f%%', round(100*v/nVox)); + end + + [betas(:,v), tvals(:,v), pvals(:,v), SE(:,v), DFmat(:,v), ... + sigma(1,v), res_v, ... + con_vals(:,v), con_t(:,v), con_p(:,v), con_se(:,v), con_df(:,v), ... + mask(v)] = fit_lme_one_voxel( ... + Ymat(v,:)', tbl, formula, fitmethod, C, nC, nFixed); + if do_resid + R(:,v) = res_v; + end + end +end + +if doverbose + fprintf('Voxelwise LME finished. Successful fits: %d / %d voxels.\n', sum(mask), nVox); + toc +end + +% ------------------------------------------------------------------------- +% Build CANlab-style outputs +% ------------------------------------------------------------------------- + +out = struct; +out.analysis_name = analysis_name; +out.formula = formula; +out.fixed_names = fixed_names; +out.C = C; +out.contrast_names = contrast_names; +out.mask = mask; +out.input_parameters = struct( ... + 'pthr', pthr, 'thresh_type', thresh_type, ... + 'doparallel', doparallel, 'fitmethod', fitmethod); +out.warnings = mywarnings; + +% --- df image (take first fixed-effect df as representative per voxel) --- +df_scalar = DFmat(1, :); % [1 x nVox] +df_img = obj; +df_img.dat = df_scalar'; +df_img.dat_descrip = 'Degrees of freedom (first fixed effect DFE)'; +out.df = df_img; + +% --- sigma image --- +sig_img = obj; +sig_img.dat = sigma'; +sig_img.dat_descrip = 'Residual std dev from LME per voxel'; +out.sigma = sig_img; + +% --- Beta maps as statistic_image --- +b_img = statistic_image; +b_img.type = 'Beta (LME)'; +b_img.dat = betas'; % [nVox x nFixed] +b_img.p = pvals'; % [nVox x nFixed] +b_img.ste = SE'; % [nVox x nFixed] +b_img.N = nObs; +b_img.volInfo = obj.volInfo; +b_img.removed_voxels = obj.removed_voxels; +b_img.removed_images = false; +b_img.image_labels = fixed_names; +b_img.dat_descrip = 'Fixed-effect betas from voxelwise LME'; +b_img = enforce_variable_types(b_img); +out.b = b_img; + +% --- T maps as statistic_image --- +t_img = statistic_image; +t_img.type = 'T (LME fixed effects)'; +t_img.dat = tvals'; % [nVox x nFixed] +t_img.p = pvals'; % [nVox x nFixed] +t_img.ste = SE'; % [nVox x nFixed] +t_img.N = nObs; +t_img.volInfo = obj.volInfo; +t_img.removed_voxels = obj.removed_voxels; +t_img.removed_images = false; +t_img.image_labels = fixed_names; +t_img.dat_descrip = 'Fixed-effect t-values from voxelwise LME'; +t_img = enforce_variable_types(t_img); + +if doverbose + fprintf('Thresholding fixed-effect t-maps at p = %.4g (%s)\n', pthr, thresh_type); +end +t_img = threshold(t_img, pthr, thresh_type); +out.t = t_img; + +% --- Residuals as fmri_data (optional) --- +if do_resid + resid_img = obj; + resid_img.dat = R'; % [nVox x nObs] + resid_img.dat_descrip = 'Residuals from voxelwise LME'; + resid_img.history{end+1} = 'Residuals from fitlme_voxelwise'; + out.resid = resid_img; +end + +% --- Contrasts (if any) --- +if nC > 0 + % Contrast beta + con_img = statistic_image; + con_img.type = 'Contrast (LME fixed effects)'; + con_img.dat = con_vals'; % [nVox x nC] + con_img.p = con_p'; % [nVox x nC] + con_img.ste = con_se'; % [nVox x nC] + con_img.N = nObs; + con_img.volInfo = obj.volInfo; + con_img.removed_voxels = obj.removed_voxels; + con_img.removed_images = false; + con_img.image_labels = contrast_names; + con_img.dat_descrip = 'Contrast beta values from voxelwise LME'; + con_img = enforce_variable_types(con_img); + out.contrast_images = con_img; + + % Contrast t + con_t_img = statistic_image; + con_t_img.type = 'T (LME contrasts)'; + con_t_img.dat = con_t'; % [nVox x nC] + con_t_img.p = con_p'; % [nVox x nC] + con_t_img.ste = con_se'; % [nVox x nC] + con_t_img.N = nObs; + con_t_img.volInfo = obj.volInfo; + con_t_img.removed_voxels = obj.removed_voxels; + con_t_img.removed_images = false; + con_t_img.image_labels = contrast_names; + con_t_img.dat_descrip = 'Contrast t-values from voxelwise LME'; + con_t_img = enforce_variable_types(con_t_img); + + if doverbose + fprintf('Thresholding contrast t-maps at p = %.4g (%s)\n', pthr, thresh_type); + end + con_t_img = threshold(con_t_img, pthr, thresh_type); + out.con_t = con_t_img; +end + +% ------------------------------------------------------------------------- +% Display (optional) +% ------------------------------------------------------------------------- +if do_display + try + orthviews(out.t); + catch + warning('orthviews failed; skipping display.'); + end +end + +end % main function + + +% ===================================================================== +% Helper: fit one voxel's LME and (optionally) contrasts +% ===================================================================== +function [b, t, p, se, df, sigma, res_v, ... + con_vals, con_t, con_p, con_se, con_df, ok] = ... + fit_lme_one_voxel(Yvec, tbl, formula, fitmethod, C, nC, nFixed) + +% Initialize with correct sizes so assignment in parfor works +b = NaN(nFixed, 1); +t = NaN(nFixed, 1); +p = NaN(nFixed, 1); +se = NaN(nFixed, 1); +df = NaN(nFixed, 1); +sigma = NaN; +res_v = NaN(height(tbl), 1); +ok = false; + +if nC > 0 + con_vals = NaN(nC,1); + con_t = NaN(nC,1); + con_p = NaN(nC,1); + con_se = NaN(nC,1); + con_df = NaN(nC,1); +else + con_vals = NaN(0,1); + con_t = NaN(0,1); + con_p = NaN(0,1); + con_se = NaN(0,1); + con_df = NaN(0,1); +end + +% All-NaN Y -> skip (returns NaNs with correct sizes) +if all(isnan(Yvec)) + return; +end + +tbl_local = tbl; +tbl_local.Y = Yvec; + +try + lme = fitlme(tbl_local, formula, 'FitMethod', fitmethod); + + [beta_hat, CovB, stats] = fixedEffects(lme, 'DFMethod', 'Residual'); + + % overwrite initialized NaNs with real values + k = numel(beta_hat); + b(1:k) = beta_hat; + t(1:k) = stats.tStat(:); + p(1:k) = stats.pValue(:); + se(1:k) = stats.SE(:); + + % DFE may be scalar or per-parameter; handle both + if isscalar(stats.DFE) + df(:) = stats.DFE; + else + df(1:k) = stats.DFE(:); + end + + res_v = residuals(lme, 'ResidualType', 'Raw'); + sigma = std(res_v); + + % Contrasts (over fixed effects) + if nC > 0 + for ci = 1:nC + Li = C(:,ci); % [nFixed x 1] + cval = Li' * beta_hat; % scalar + cse = sqrt( Li' * CovB * Li ); + if cse == 0 || isnan(cse) + ct = NaN; + cp = NaN; + else + ct = cval / cse; + dfe_con = stats.DFE; % usually scalar + if isscalar(dfe_con) + cp = 2 * (1 - tcdf(abs(ct), dfe_con)); + else + dfe_con = dfe_con(1); + cp = 2 * (1 - tcdf(abs(ct), dfe_con)); + end + end + con_vals(ci,1) = cval; + con_se(ci,1) = cse; + con_t(ci,1) = ct; + con_p(ci,1) = cp; + if isscalar(stats.DFE) + con_df(ci,1) = stats.DFE; + else + con_df(ci,1) = stats.DFE(1); + end + end + end + + ok = true; +catch + ok = false; +end + +end diff --git a/CanlabCore/@fmri_data/fmri_data.m b/CanlabCore/@fmri_data/fmri_data.m index 2ca96511..ce0c85a9 100644 --- a/CanlabCore/@fmri_data/fmri_data.m +++ b/CanlabCore/@fmri_data/fmri_data.m @@ -628,16 +628,12 @@ % Now extract the actual data from the mask switch spm('Ver') - - case {'SPM25' 'SPM12','SPM8', 'SPM5'} - [imgdat, ~, image_info_struct] = iimg_get_data(maskobj.volInfo, image_names, 'single', verbosestr, 'noexpand'); - case {'SPM2', 'SPM99'} - % legacy, for old SPM + % legacy SPM [imgdat, ~, image_info_struct] = iimg_get_data(maskobj.volInfo, image_names, 'single', verbosestr); - otherwise - error('Unknown version of SPM! Update code, check path, etc.'); + % SPM5+, including any future versions + [imgdat, ~, image_info_struct] = iimg_get_data(maskobj.volInfo, image_names, 'single', verbosestr, 'noexpand'); end imgdat = imgdat'; diff --git a/CanlabCore/@fmri_data/get_model_encoding_map.m b/CanlabCore/@fmri_data/get_model_encoding_map.m index c326087e..4261b040 100644 --- a/CanlabCore/@fmri_data/get_model_encoding_map.m +++ b/CanlabCore/@fmri_data/get_model_encoding_map.m @@ -1,53 +1,71 @@ function [beta,p_values] = get_model_encoding_map(roi, latent_timeseries_pathway) -% -% This function computes a model encoding map by performing linear -% regression to understand how the latent timeseries data from MPathI -% (model_brain_pathway.m) relates to voxel activity data (ROI). It calculates -% beta coefficients for each voxel, revealing how fluctuations in the -% latent timeseries impact neural activity patterns and consisting model -% encoding map -% -% [Input]: -% - roi: Actual activity timeseries data [TR x voxels] -% - latent_timeseries_pathway: Latent timeseries data from MPathI (XS or -% YS) [TR x pathways] (e.g., two columns represent on and off-target conditions) -% -% [Output]: -% - beta: Beta coefficient representing the linear relationship between -% the activity of each voxel and the latent timeseries pathways -% [voxels x pathways] -% - p_values: p-values for the significance of each beta coefficient, -% indicating the strength of the relationship. -% -% It quantifies the change in the activity data for each voxel associated -% with a one-unit change in the latent timeseries data. In other words, it -% indicates the strength and direction of the linear relationship between -% the activity of each voxel and the latent timeseries pathway. -% -% -% [Examples] -% ===== -% stats = model_brain_pathway(preproc_dat, source_one,source_two,target_one,target_two, 'Indices', wh_run*(1:size(wh_run,2))'); -% -% score{1} = [stats.latent_timeseries_source(:,1), stats.latent_timeseries_source(:,3)]; % source1 -% score{2} = [stats.latent_timeseries_target(:,1), stats.latent_timeseries_target(:,2)]; % target1 -% score{3} = [stats.latent_timeseries_source(:,4), stats.latent_timeseries_source(:,2)]; % source2 -% score{4} = [stats.latent_timeseries_target(:,4), stats.latent_timeseries_target(:,3)]; % target2 -% -% roi{1} = stats.source_one_obj.dat'; -% roi{2} = stats.target_one_obj.dat'; -% roi{3} = stats.source_two_obj.dat'; -% roi{4} = stats.target_two_obj.dat'; -% -% for k = 1:4 % S1, T1, S2, T2 -% [b_temp, p_temp] = get_model_encoding_map(roi{k}, score{k}); -% end -% ===== -% -% Also see model_brain_pathway.m -% -% Developed by Tor and Byeol, 2022-24 -% +% get_model_encoding_map Voxelwise encoding map relating ROI activity to MPathI latent time series. +% +% :Usage: +% :: +% +% [beta, p_values] = get_model_encoding_map(roi, latent_timeseries_pathway) +% +% Computes a model encoding map by performing linear regression to +% understand how the latent time-series data from MPathI +% (model_brain_pathway.m) relate to voxel activity data (ROI). For +% each voxel and each pathway, beta coefficients and p-values are +% returned, revealing how fluctuations in the latent time series impact +% neural activity patterns and forming the model encoding map. +% +% Each beta quantifies the change in activity at a given voxel +% associated with a one-unit change in the corresponding latent time +% series, i.e., the strength and direction of the linear relationship +% between voxel activity and the latent pathway. +% +% :Inputs: +% +% **roi:** +% Actual activity time-series data, shape [TR x voxels]. +% +% **latent_timeseries_pathway:** +% Latent time-series data from MPathI (XS or YS), shape +% [TR x pathways] (e.g., two columns representing on- and +% off-target conditions). +% +% :Outputs: +% +% **beta:** +% Beta coefficients describing the linear relationship between +% each voxel's activity and each latent time series pathway, +% shape [voxels x pathways]. +% +% **p_values:** +% p-values for the significance of each beta coefficient, +% indicating the strength of each relationship. +% +% :Examples: +% :: +% +% stats = model_brain_pathway(preproc_dat, ... +% source_one, source_two, target_one, target_two, ... +% 'Indices', wh_run * (1:size(wh_run, 2))'); +% +% score{1} = [stats.latent_timeseries_source(:,1), stats.latent_timeseries_source(:,3)]; % source1 +% score{2} = [stats.latent_timeseries_target(:,1), stats.latent_timeseries_target(:,2)]; % target1 +% score{3} = [stats.latent_timeseries_source(:,4), stats.latent_timeseries_source(:,2)]; % source2 +% score{4} = [stats.latent_timeseries_target(:,4), stats.latent_timeseries_target(:,3)]; % target2 +% +% roi{1} = stats.source_one_obj.dat'; +% roi{2} = stats.target_one_obj.dat'; +% roi{3} = stats.source_two_obj.dat'; +% roi{4} = stats.target_two_obj.dat'; +% +% for k = 1:4 % S1, T1, S2, T2 +% [b_temp, p_temp] = get_model_encoding_map(roi{k}, score{k}); +% end +% +% :See also: +% - model_brain_pathway, model_mpathi +% +% .. +% Developed by Tor and Byeol, 2022-24. +% .. % Get the dimensions of the input data [tr_n, voxels_n] = size(roi); % [TR x voxels] pathway_n = size(latent_timeseries_pathway,2); % [TR x pathways] diff --git a/CanlabCore/@fmri_data/horzcat.m b/CanlabCore/@fmri_data/horzcat.m index fc0069b7..c5992730 100644 --- a/CanlabCore/@fmri_data/horzcat.m +++ b/CanlabCore/@fmri_data/horzcat.m @@ -1,20 +1,47 @@ function c = horzcat(varargin) -% Implements the horzcat ([a b]) operator on image_vector objects across voxels. -% Requires that each object has an equal number of columns and voxels +% horzcat Implements the horzcat ([a b]) operator on fmri_data objects. % % :Usage: % :: % -% function s = horzcat(varargin) +% c = horzcat(dat1, dat2, ...) +% c = [dat1 dat2 ...] % equivalent % -% :Example: +% Concatenates fmri_data objects horizontally across the image (column) +% dimension. Each input object's .dat is expected to have the same number +% of voxels (rows); the resulting object has the union of images in +% column order. If every input has a non-empty .X (predictors), the .X +% matrices are vertically concatenated; same for .Y (outcomes). If any +% input is missing X or Y, those fields are dropped from the result. +% +% :Inputs: +% +% **varargin:** +% Two or more fmri_data objects with matching voxel counts. All +% inputs must be of class fmri_data; passing any non-fmri_data +% argument raises an error. +% +% :Outputs: +% +% **c:** +% An fmri_data object with .dat = [dat1.dat, dat2.dat, ...]. If +% every input had a populated .X / .Y, those are stacked +% vertically and attached. +% +% :Examples: % :: % -% c = [dat1 dat2]; +% c = [dat1 dat2]; +% c = horzcat(dat1, dat2, dat3); +% +% :See also: +% - cat (concatenation method on fmri_data) +% - get_wh_image (subset of images) % % .. -% Programmer Notes -% Created 3/14/14 by Luke Chang for image_vector; updated for fmri_data 8/2015 Yoni Ashar +% Programmer Notes: +% Created 3/14/14 by Luke Chang for image_vector; updated for fmri_data +% 8/2015 Yoni Ashar. % .. hasX = 1; X = []; diff --git a/CanlabCore/@fmri_data/hrf_fit.m b/CanlabCore/@fmri_data/hrf_fit.m index c182f6a2..2e792b31 100644 --- a/CanlabCore/@fmri_data/hrf_fit.m +++ b/CanlabCore/@fmri_data/hrf_fit.m @@ -1,30 +1,51 @@ function [params_obj, hrf_obj, params_obj_dat, hrf_obj_dat] = hrf_fit(obj,TR,Runc,T,method,mode, varargin) -% HRF estimation on fmri_data class object +% hrf_fit Voxelwise HRF estimation on an fmri_data object. % -% HRF estimation function for a single voxel; +% :Usage: +% :: +% +% [params_obj, hrf_obj, params_obj_dat, hrf_obj_dat] = ... +% hrf_fit(obj, TR, Runs, T, method, mode) % -% Implemented methods include: IL-model (Deterministic/Stochastic), FIR -% (Regular/Smooth), and HRF (Canonical/+ temporal/+ temporal & dispersion) +% HRF estimation routine that runs voxelwise on an fmri_data object. +% Implemented methods include: IL-model (deterministic / stochastic), +% FIR (regular / smooth), and HRF (canonical, + temporal, +% + temporal & dispersion). % % :Inputs: % -% **obj** -% fMRI object or cell-array of fMRI-objects +% **obj:** +% fMRI object or cell array of fMRI objects. +% +% **TR:** +% Time resolution (s). +% +% **Runs:** +% Experimental design (onsets / regressors), passed through to +% the underlying single-voxel fit routines. +% +% **T:** +% Length of the estimated HRF in seconds. +% +% **method:** +% Model type: 'FIR', 'IL', or 'CHRF'. +% +% **mode:** +% Mode selector for the chosen method (see :Model Types: below). % -% **TR** -% time resolution +% :Outputs: % -% **Runs** -% expermental design +% **params_obj:** +% fmri_data / statistic_image with fitted HRF parameters per voxel. % -% **T** -% length of estimated HRF ij seconds +% **hrf_obj:** +% fmri_data with the estimated HRF time courses per voxel. % -% **type** -% Model type: 'FIR', 'IL', or 'CHRF' +% **params_obj_dat:** +% Raw parameter matrix (.dat) corresponding to params_obj. % -% **mode** -% Mode +% **hrf_obj_dat:** +% Raw HRF time-course matrix corresponding to hrf_obj. % % :Model Types: % diff --git a/CanlabCore/@fmri_data/model_mpathi.m b/CanlabCore/@fmri_data/model_mpathi.m index b4775887..725933a7 100644 --- a/CanlabCore/@fmri_data/model_mpathi.m +++ b/CanlabCore/@fmri_data/model_mpathi.m @@ -1,111 +1,138 @@ function stats = model_mpathi(obj,source_mask,target_mask,varargin) -% Models functional coupling between two brain regions using cross-validated -% partial least squares (PLS) regression (Original code:model_brain_pathway.m) -% -% ------------------------------------------------------------------------------ -% OVERVIEW -% ------------------------------------------------------------------------------ -% This script is a streamlined and up-to-date version of the original -% [model_brain_pathway.m] function. Unlike the original implementation, which -% compared traditional functional connectivity (based on ROI-averaged -% signals) with the multivariate Pathway Interaction (MPathI) framework +% model_mpathi Cross-validated multivariate pathway-level connectivity (MPathI) between two regions. +% +% :Usage: +% :: +% +% stats = model_mpathi(obj, source_mask, target_mask, ... +% 'Indices', wh_subject, 'nboot', 1000, 'plot') +% +% Models functional coupling between two brain regions using +% cross-validated partial least squares (PLS) regression (original code: +% model_brain_pathway.m). +% +% This function is a streamlined and up-to-date version of the original +% model_brain_pathway.m. Unlike that implementation, which compared +% traditional functional connectivity (based on ROI-averaged signals) +% with the multivariate Pathway Interaction (MPathI) framework % described in Kragel et al. (2021, Neuron), the present script focuses % exclusively on the MPathI approach. In addition, this implementation % estimates a single directed pathway between one source region and one -% target region, rather than modeling four on-target and off-target +% target region, rather than modeling four on-target / off-target % pathways as in model_brain_pathway. -% -% This function estimates multivariate pathway-level connectivity between a -% source region (X) and a target region (Y) using Partial Least Squares (PLS). -% The model identifies latent population activity in each region whose -% time series covary maximally across observations (e.g., trials or timepoints). -% -% -% ------------------------------------------------------------------------------ -% obj : -% fmri_data object containing images (e.g., trials or timepoints) -% -% source_mask : -% fmri_data object with binary mask defining the source region (X) -% -% target_mask : -% fmri_data object with binary mask defining the target region (Y) -% -% ------------------------------------------------------------------------------ -% OPTIONAL NAME–VALUE PAIRS -% ------------------------------------------------------------------------------ -% 'Indices' : -% Integer vector (n_images × 1) defining cross-validation folds and -% bootstrap blocks (e.g., subject ID). Default: 10-fold CV. -% -% 'Align' : -% Perform hyperalignment across subjects (requires 'Indices'). -% -% 'nboot' : -% Number of block bootstrap samples for voxelwise inference on weights. -% -% 'plot' : -% Plot cross-validated latent correlations. -% -% 'noroi' : -% Do not return masked ROI data in the output (reduces output size). -% -% ------------------------------------------------------------------------------ -% Convention used throughout: -% X, Y : [images × voxels] -% T, U : [images × 1] latent time series -% Z, V : [voxels × 1] spatial weights -% ------------------------------------------------------------------------------ -% OUTPUT -% ------------------------------------------------------------------------------ -% stats : -% Structure containing: -% -% Cross-validated results: -% • stats.xval.latent_correlations – corr(T̂, Û) per fold -% • stats.xval.T_latent_timeseries – predicted source latent time series -% • stats.xval.U_latent_timeseries – predicted target latent time series -% -% Summary statistics: -% • stats.overall_xval_r – correlation across all held-out data -% • stats.overall_xval_dot – dot product of latent time series -% -% Whole-sample estimates: -% • stats.whole.T_latent_timeseries - source latent time series -% • stats.whole.U_latent_timeseries - target latent time series -% • stats.whole.Z_weights – source voxel weights -% • stats.whole.V_weights – target voxel weights -% -% Bootstrap results (optional): -% • stats.PLS_bootstrap_stats_Z -% • stats.PLS_bootstrap_stats_V -% -% ------------------------------------------------------------------------------ -% INTERPRETING RESULTS -% ------------------------------------------------------------------------------ -% • The primary measure of pathway strength is stats.overall_xval_r. -% • Cross-validated correlations reflect out-of-sample predictive coupling. -% • Whole-sample weights should be interpreted descriptively unless -% supported by bootstrap inference. -% -% ------------------------------------------------------------------------------ -% Author and copyright information: -% ------------------------------------------------------------------------------ -% -% Copyright (C) 2026 Byeol Kim Lux -% -% This program is free software: you can redistribute it and/or modify -% it under the terms of the GNU General Public License as published by -% the Free Software Foundation, either version 3 of the License, or -% (at your option) any later version. -% -% This program is distributed in the hope that it will be useful, -% but WITHOUT ANY WARRANTY; without even the implied warranty of -% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -% GNU General Public License for more details. -% -% You should have received a copy of the GNU General Public License -% along with this program. If not, see . +% +% The model estimates multivariate pathway-level connectivity between a +% source region (X) and a target region (Y) using PLS. It identifies +% latent population activity in each region whose time series covary +% maximally across observations (e.g., trials or time points). +% +% :Conventions used throughout: +% +% :: +% +% X, Y : [images x voxels] +% T, U : [images x 1] latent time series +% Z, V : [voxels x 1] spatial weights +% +% :Inputs: +% +% **obj:** +% fmri_data object containing images (e.g., trials or time points). +% +% **source_mask:** +% fmri_data object with a binary mask defining the source region X. +% +% **target_mask:** +% fmri_data object with a binary mask defining the target region Y. +% +% :Optional Inputs: +% +% **'Indices', integer vector:** +% n_images x 1 vector defining cross-validation folds and +% bootstrap blocks (e.g., subject ID). Default: 10-fold CV +% chosen by crossvalind('Kfold', n_images, 10). +% +% **'Align':** +% Perform hyperalignment across subjects (requires 'Indices'). +% +% **'nboot', N:** +% Number of block-bootstrap samples for voxelwise inference on +% the source / target weight maps. +% +% **'plot':** +% Plot cross-validated latent correlations. +% +% **'noroi':** +% Do not return masked ROI data in the output (reduces output +% size). +% +% :Outputs: +% +% **stats:** +% Structure containing: +% +% - Cross-validated results: +% +% * stats.xval.latent_correlations: corr(That, Uhat) per fold. +% * stats.xval.T_latent_timeseries: predicted source latent +% time series. +% * stats.xval.U_latent_timeseries: predicted target latent +% time series. +% +% - Summary statistics: +% +% * stats.overall_xval_r: correlation across all held-out +% data. +% * stats.overall_xval_dot: dot product of latent time +% series. +% +% - Whole-sample estimates: +% +% * stats.whole.T_latent_timeseries: source latent time series. +% * stats.whole.U_latent_timeseries: target latent time series. +% * stats.whole.Z_weights: source voxel weights. +% * stats.whole.V_weights: target voxel weights. +% +% - Bootstrap results (when 'nboot' is supplied): +% +% * stats.PLS_bootstrap_stats_Z +% * stats.PLS_bootstrap_stats_V +% +% :Interpreting Results: +% +% - The primary measure of pathway strength is stats.overall_xval_r. +% - Cross-validated correlations reflect out-of-sample predictive +% coupling. +% - Whole-sample weights should be interpreted descriptively unless +% supported by bootstrap inference. +% +% :References: +% Kragel, P. A., Cetin, B., Tor, W., et al. (2021). Multivariate +% pathway interactions reveal functional coupling between brain +% regions. Neuron. +% +% :See also: +% - model_brain_pathway (original four-pathway implementation) +% - get_model_encoding_map +% +% .. +% Author and copyright information: +% +% Copyright (C) 2026 Byeol Kim Lux +% +% This program is free software: you can redistribute it and/or modify +% it under the terms of the GNU General Public License as published by +% the Free Software Foundation, either version 3 of the License, or +% (at your option) any later version. +% +% This program is distributed in the hope that it will be useful, +% but WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +% GNU General Public License for more details. +% +% You should have received a copy of the GNU General Public License +% along with this program. If not, see . +% .. %% Get defaults and initialize user inputs if any(strcmp(varargin, 'plot')) diff --git a/CanlabCore/@fmri_data/neurosynth_lexical_plot.m b/CanlabCore/@fmri_data/neurosynth_lexical_plot.m index f989ab05..db9489f1 100644 --- a/CanlabCore/@fmri_data/neurosynth_lexical_plot.m +++ b/CanlabCore/@fmri_data/neurosynth_lexical_plot.m @@ -1,25 +1,50 @@ function [image_by_feature_correlations, top_feature_tables]=neurosynth_lexical_plot(fmri_data) - % NEUROSYNTH_LEXICAL_PLOT Plots the correlations between fMRI data and lexical features. + % neurosynth_lexical_plot Plot correlations between fmri_data images and Neurosynth lexical features. % - % This function takes fMRI data and calculates the correlation between - % the data and lexical features using the Neurosynth toolbox. It then - % creates plots to visualize these correlations. + % :Usage: + % :: % - % USAGE: - % [image_by_feature_correlations, top_feature_tables] = neurosynth_lexical_plot(fmri_data, varargin) + % [image_by_feature_correlations, top_feature_tables] = ... + % neurosynth_lexical_plot(fmri_data) % - % INPUTS: - % fmri_data : fMRI data to be analyzed. + % Takes fMRI data and calculates the correlation between the data and + % lexical features using the Neurosynth toolbox (via + % neurosynth_feature_labels), then creates a horizontal bar plot + % to visualize the strongest positive and negative associations. % - % OUTPUTS: - % image_by_feature_correlations : Correlations between images and features. - % top_feature_tables : Tables containing the top features and their scores. + % :Inputs: % - % EXAMPLES: - % [correlations, tables] = neurosynth_lexical_plot(fmri_data); + % **fmri_data:** + % An fmri_data object containing the image(s) to annotate + % against the Neurosynth lexicon. Treated as a set of + % replicates; each is correlated with each feature map. % - % Authors: Michael Sun, Ph.D. - % Date: 05/30/2024 + % :Outputs: + % + % **image_by_feature_correlations:** + % Matrix of correlations between images and Neurosynth + % features (as returned by neurosynth_feature_labels). + % + % **top_feature_tables:** + % Cell array of tables containing the top features (high and + % low correlations) and their t-scores per image. + % + % :Examples: + % :: + % + % [correlations, tables] = neurosynth_lexical_plot(fmri_obj); + % + % :See also: + % - neurosynth_feature_labels (feature correlation back-end) + % - annotate_continuous_neuroimage_maps + % + % .. + % Authors: Michael Sun, Ph.D. + % Date: 05/30/2024 + % + % Future: Add a 'between' option for separate image analysis + % instead of the replicates default. + % .. % Future: Add a between option for separate image analysis instead of diff --git a/CanlabCore/@fmri_data/normalize_gm_by_wm_csf.m b/CanlabCore/@fmri_data/normalize_gm_by_wm_csf.m index 9c4520ea..27bf8339 100644 --- a/CanlabCore/@fmri_data/normalize_gm_by_wm_csf.m +++ b/CanlabCore/@fmri_data/normalize_gm_by_wm_csf.m @@ -1,104 +1,129 @@ function [obj_out, statstab] = normalize_gm_by_wm_csf(obj, varargin) +% normalize_gm_by_wm_csf Shift- and scale-normalize gray matter voxels using WM/CSF references. +% +% :Usage: +% :: +% +% [obj_out, statstab] = normalize_gm_by_wm_csf(obj) +% [obj_out, statstab] = normalize_gm_by_wm_csf(obj, ... +% 'do_scale', true, 'log_scale', false, 'trim_pct', 5, ... +% 'mask_files', masks_cell) +% % Normalize gray-matter voxel intensities across subjects in an fmri_data % object by: -% (1) Removing a subject-specific additive shift estimated from CSF/WM -% medians -% (2) Correcting a subject-specific multiplicative scale estimated from -% MADs in GM, WM, and CSF +% +% 1. Removing a subject-specific additive shift estimated from CSF/WM +% medians. +% 2. Correcting a subject-specific multiplicative scale estimated from +% MADs in GM, WM, and CSF. % % The method: +% % - Uses canonical masks for GM, WM, and CSF: -% {'gray_matter_mask_sparse.img', ... -% 'canonical_white_matter.img', ... -% 'canonical_ventricles.img'} +% +% :: +% +% {'gray_matter_mask_sparse.img', ... +% 'canonical_white_matter.img', ... +% 'canonical_ventricles.img'} +% % - Resamples masks into the space of the fmri_data object using -% RESAMPLE_SPACE to ensure voxel alignment. -% - Calls NORMALIZE_GM_SHIFT_SCALE (voxel-level function) on the data. -% - Returns: -% * an fmri_data object with GM voxels shift- and scale-normalized, -% and non-GM voxels left unchanged except that they are not further -% processed -% * a MATLAB table containing all values from the STATS output of -% normalize_gm_shift_scale, appended to the existing -% metadata_table. -% -% USAGE -% [obj_out, statstab] = normalize_gm_by_wm_csf(obj, ... -% 'do_scale', true, 'log_scale', false, 'trim_pct', 5, ... -% 'mask_files', masks_cell); -% -% INPUTS -% obj : fmri_data object with .dat of size [V x S] -% - V = number of voxels -% - S = number of images/subjects -% -% OPTIONAL NAME/VALUE INPUTS -% 'do_scale' : logical (default = true) -% - Passed to NORMALIZE_GM_SHIFT_SCALE. -% - If true: estimate and apply multiplicative scale -% normalization. -% - If false: skip scale estimation and apply shift-only -% normalization. -% -% 'log_scale' : logical (default = false) -% - Passed to NORMALIZE_GM_SHIFT_SCALE. -% - If true: log-scale regression for scale model -% log(r_GM) ~ log(r_CSF) + log(r_WM) -% - If false: linear scale regression -% r_GM ~ r_CSF + r_WM -% -% 'trim_pct' : scalar (default = 5) -% - Percentage trimmed from lower and upper tails when -% estimating medians/MADs in each tissue. -% -% 'mask_files': 1 x 3 cell array of mask filenames -% (default): -% {'gray_matter_mask_sparse.img', ... -% 'canonical_white_matter.img', ... -% 'canonical_ventricles.img'} -% -% OUTPUTS -% obj_out : fmri_data object -% - .dat is V x S, with GM voxels normalized by the shift/scale -% model; non-GM voxels are copied from the input. -% - .metadata_table is the input metadata_table with new -% columns appended containing all (subject-level) values from -% STATS. -% -% statstab : MATLAB table (S rows) -% - All values from STATS struct are represented as columns. -% - This same table is appended to obj_out.metadata_table. -% -% NOTES -% - This method does not reduce the number of voxels; it keeps the full -% spatial geometry but only modifies GM voxels in .dat. +% resample_space to ensure voxel alignment. +% - Calls normalize_gm_shift_scale (voxel-level function) on the +% data. +% - Returns an fmri_data object with GM voxels shift- and +% scale-normalized (non-GM voxels are copied from the input +% unchanged), and a MATLAB table of all per-subject statistics from +% the STATS output of normalize_gm_shift_scale, appended to the +% existing metadata_table. +% +% :Inputs: +% +% **obj:** +% fmri_data object with .dat of size [V x S], where V = number of +% voxels and S = number of images / subjects. +% +% :Optional Inputs: +% +% **'do_scale', logical:** +% Default: true. Passed to normalize_gm_shift_scale. +% If true, estimate and apply multiplicative scale normalization. +% If false, skip scale estimation and apply shift-only +% normalization. +% +% **'log_scale', logical:** +% Default: false. Passed to normalize_gm_shift_scale. +% If true, log-scale regression is used for the scale model +% log(r_GM) ~ log(r_CSF) + log(r_WM). If false, linear +% regression r_GM ~ r_CSF + r_WM. +% +% **'trim_pct', scalar:** +% Default: 5. Percentage trimmed from the lower and upper +% tails when estimating medians / MADs in each tissue. +% +% **'mask_files', 1x3 cellstr:** +% Default: +% +% :: +% +% {'gray_matter_mask_sparse.img', ... +% 'canonical_white_matter.img', ... +% 'canonical_ventricles.img'} +% +% Resolved with which() if a bare filename is supplied. +% +% :Outputs: +% +% **obj_out:** +% fmri_data object. .dat is V x S, with GM voxels normalized +% by the shift/scale model; non-GM voxels are copied from the +% input. .metadata_table is the input metadata_table with new +% columns appended containing all subject-level values from STATS. +% +% **statstab:** +% MATLAB table with S rows. Every value from the STATS struct is +% represented as one or more columns. The same table is appended +% to obj_out.metadata_table. +% +% :Notes: +% +% - This method does not reduce the number of voxels; it keeps the +% full spatial geometry but only modifies GM voxels in .dat. % - If you prefer to hard-mask to GM (retain only GM voxels in .dat), % you can add an additional step to subset voxels by the GM mask. % -% Examples: +% :Examples: +% :: +% +% imgs = load_image_set('emotionreg'); +% +% % T-test on un-normalized data: +% t = ttest(imgs); +% histogram(t); +% set(gcf, 'Tag', 'unnormalized'); % -% imgs = load_image_set('emotionreg'); +% % Normalize and re-run t-test +% imgs_normalized = normalize_gm_by_wm_csf(imgs); +% t2 = ttest(imgs_normalized); +% histogram(t2); % -% % T-test on un-normalized data: -% t = ttest(imgs) -% histogram(t) -% set(gcf, 'Tag', 'unnormalized'); +% % Compare the t-values: +% figure; plot(t.dat, t2.dat, '.'); +% hold on; plot([-10 10], [-10 10], '--', 'Color', 'k'); +% ylabel('t-values after normalization'); +% xlabel('t-values before normalization'); % -% % Normalize and re-run t-test -% imgs_normalized = normalize_gm_by_wm_csf(imgs); -% t2 = ttest(imgs_normalized) -% histogram(t2) +% % or: +% h = image_scatterplot(t, t2, 'pvaluebox', 0.005, 'colorpoints'); % -% % Compare the t-values: -% figure; plot(t.dat, t2.dat, '.'); -% hold on; plot([-10 10], [-10 10], '--', 'Color', 'k'); -% ylabel('t-values after normalization'); xlabel('t-values before normalization'); -% -% % or: -% h = image_scatterplot(t, t2, 'pvaluebox', 0.005, 'colorpoints'); +% :See also: +% - normalize_gm_shift_scale (voxel-level function called internally) +% - rescale, windsorize (other intensity-normalization options) % -% Author: Tor Wager + ChatGPT5.2 -% Date: 2025-12-09 +% .. +% Author: Tor Wager + ChatGPT5.2 +% Date: 2025-12-09 +% .. % ------------------------------------------------------------------------- % Parse inputs diff --git a/CanlabCore/@fmri_data/runRestMetrics.m b/CanlabCore/@fmri_data/runRestMetrics.m index de71bf31..e29bc272 100644 --- a/CanlabCore/@fmri_data/runRestMetrics.m +++ b/CanlabCore/@fmri_data/runRestMetrics.m @@ -1,82 +1,132 @@ function results = runRestMetrics(dataObj, headerInfo, TR, varargin) -% runRestMetrics Compute (optionally) ALFF/fALFF and/or ReHo on -% preprocessed data object from CanlabTool using DPABI toolbox (https://rfmri.org/DPABI) -% citation: Yan, C. G., Wang, X. D., Zuo, X. N., & Zang, Y. F. (2016). DPABI: data processing & analysis for (resting-state) brain imaging. Neuroinformatics, 14, 339-351. -% -% results = runRestMetrics(dataObj, headerInfo, TR, ...) -% -% Inputs: -% dataObj – an fmri_data object (already nuisance‑regressed & detrended) -% headerInfo – niftiinfo struct for your target space -% TR – repetition time (s) -% -% Name-Value Pairs: -% 'MaskFile' – mask.nii The toolbox only compute the values within the Mask. We'll create a mask from the frist image from your image dataset (default: 'Resampled_CanlabMask.nii') -% 'HighCutoff_ALFF' – high cutoff for ALFF (Hz) (default: 0.1) -% 'LowCutoff_ALFF' – low cutoff for ALFF (Hz) (default: 0.01) -% 'TemporalMask' – scrubbing mask ('' = none) (default: '') -% 'ScrubbingMethod' – passed to y_alff_falff/y_reho (default: 1) -% 'NVoxel' – # of voxels for ReHo (default: 27) -% 'Band_ReHo' – ReHo bandpass [low high] Hz (default: [0.01 0.08]) -% 'IsNeedDetrend' – ReHo detrend flag (default: true) -% 'ScrubbingTiming' – passed to y_reho (default: []) -% 'ComputeALFF' – true/false (default: true) -% 'ComputeReHo' – true/false (default: true) -% 'OutDir' – folder to write .nii files (default: pwd) -% 'ALFFFile' – filename for ALFF output (default: 'ALFF.nii') -% 'ReHoFile' – filename for ReHo output (default: 'ReHo.nii') +% runRestMetrics Compute resting-state metrics (ALFF, fALFF, ReHo) via the DPABI toolbox. +% +% :Usage: +% :: +% +% results = runRestMetrics(dataObj, headerInfo, TR, ...) +% +% Computes (optionally) ALFF / fALFF and / or ReHo on a preprocessed +% fmri_data object using the DPABI toolbox +% (https://rfmri.org/DPABI). Writes the resulting maps to disk as +% NIfTI files and returns a results struct. +% +% :Inputs: +% +% **dataObj:** +% fmri_data object that has already been nuisance-regressed and +% detrended. +% +% **headerInfo:** +% niftiinfo struct describing the target space. +% +% **TR:** +% Repetition time in seconds. +% +% :Optional Inputs: +% +% **'MaskFile':** +% Path to a mask NIfTI; the toolbox computes values only within +% the mask. If left at default, a mask is created from the first +% image in the input dataset. Default: 'Resampled_CanlabMask.nii'. +% +% **'HighCutoff_ALFF':** +% High cutoff for ALFF (Hz). Default: 0.1. +% +% **'LowCutoff_ALFF':** +% Low cutoff for ALFF (Hz). Default: 0.01. +% +% **'TemporalMask':** +% Scrubbing mask ('' = none). Default: ''. +% +% **'ScrubbingMethod':** +% Passed to y_alff_falff / y_reho. Default: 1. +% +% **'NVoxel':** +% Number of voxels in the ReHo neighbourhood. Default: 27. +% +% **'Band_ReHo':** +% ReHo bandpass [low high] in Hz. Default: [0.01 0.08]. +% +% **'IsNeedDetrend':** +% ReHo detrend flag. Default: true. +% +% **'ScrubbingTiming':** +% Passed to y_reho. Default: []. +% +% **'ComputeALFF':** +% Logical, whether to compute ALFF/fALFF. Default: true. +% +% **'ComputeReHo':** +% Logical, whether to compute ReHo. Default: true. +% +% **'OutDir':** +% Folder to write .nii files into. Default: pwd. +% +% **'ALFFFile':** +% Filename for the ALFF output. Default: 'ALFF.nii'. +% +% **'ReHoFile':** +% Filename for the ReHo output. Default: 'ReHo.nii'. +% +% :Outputs: +% +% **results:** +% Struct with fields for each computed map and the output +% filenames: +% +% - .ALFFBrain / .fALFFBrain +% - .ALFFFile +% - .ReHoBrain +% - .ReHoFile +% - .Header +% % :Examples: % :: -% % ------------------------------------------------------------------------- -% % Load a multi-study dataset, rescale it, and identify/plot outliers -% % Use 'notimeseries' option because this is not a time series dataset -% -% obj = load_image_set('kragel18_alldata'); -% obj2 = rescale(obj, 'l2norm_images'); % normalize heterogeneous datasets -% [est_outliers_uncorr, est_outliers_corr, outlier_tables] = outliers(obj2, 'notimeseries'); % -% % ------------------------------------------------------------------------- +% % Quick outlier-detection sketch on a multi-study dataset +% obj = load_image_set('kragel18_alldata'); +% obj2 = rescale(obj, 'l2norm_images'); +% [est_outliers_uncorr, est_outliers_corr, outlier_tables] = ... +% outliers(obj2, 'notimeseries'); % - +% % Example 1: Load example images, create a mask, and run rest metrics +% fname = which('swrsub-sid001567_task-pinel_acq-s1p2_run-03_bold.nii.gz'); +% obj = fmri_data(fname); +% mask = get_wh_image(obj, 1); % -% Output: -% results – struct with fields for each computed map & filenames: -% .ALFFBrain, .fALFFBrain, .ALFFFile, .ReHoBrain, .ReHoFile, .Header - -% Examples and help: -% ------------------------------------------------------------------------- -% -% -% Example 1: Load example images from CanlabCore and create image mask -% based on this image set -% -% fname = which('swrsub-sid001567_task-pinel_acq-s1p2_run-03_bold.nii.gz'); -% obj = fmri_data(fname); -% mask=get_wh_image(obj,1); -% -% fname = 'mask_Example.nii'; % outname file name. the extension (.nii, .img) determines the format. -% write(mask, 'fname', fname,'overwrite'); -% -% % Get the TR -% -% json_struct = jsondecode(fileread(which('sub-sid001567_task-pinel_acq-s1p2_run-03_bold.json'))); -% TR = json_struct.RepetitionTime; -% -% hdr = obj.volInfo; -% from your fmri_data -% -% results = runRestMetrics( ... -% obj, hdr, TR, ... -% 'MaskFile' ,fname, ... -% 'NVoxel' ,27, ... -% 'HighCutoff_ALFF' ,0.1, ... -% 'LowCutoff_ALFF' ,0.01, ... -% 'Band_ReHo' ,[0.01 0.08], ... -% 'OutDir' ,'C:\KeBo_Work\Restingstate_Analysistool\Result', ... -% 'ALFFFile' ,'Alff001'); - -% Programmers' notes: -% Ke Bo, 5/28/25 : Add an example code for running this function +% fname = 'mask_Example.nii'; % output file name; extension picks format +% write(mask, 'fname', fname, 'overwrite'); +% +% % Get the TR from a sidecar JSON +% json_struct = jsondecode(fileread(... +% which('sub-sid001567_task-pinel_acq-s1p2_run-03_bold.json'))); +% TR = json_struct.RepetitionTime; +% +% hdr = obj.volInfo; +% +% results = runRestMetrics( ... +% obj, hdr, TR, ... +% 'MaskFile' , fname, ... +% 'NVoxel' , 27, ... +% 'HighCutoff_ALFF' , 0.1, ... +% 'LowCutoff_ALFF' , 0.01, ... +% 'Band_ReHo' , [0.01 0.08], ... +% 'OutDir' , 'C:\KeBo_Work\Restingstate_Analysistool\Result', ... +% 'ALFFFile' , 'Alff001'); +% +% :References: +% Yan, C. G., Wang, X. D., Zuo, X. N., & Zang, Y. F. (2016). DPABI: +% data processing & analysis for (resting-state) brain imaging. +% Neuroinformatics, 14, 339-351. +% +% :See also: +% - canlab_connectivity_preproc (preprocessing pipeline upstream of this) +% +% .. +% Programmers' notes: +% Ke Bo, 5/28/25: Added example code for running this function. +% .. % parse inputs p = inputParser; diff --git a/CanlabCore/@fmri_data/saveplots.m b/CanlabCore/@fmri_data/saveplots.m index ce8e6bff..5ff47ee6 100644 --- a/CanlabCore/@fmri_data/saveplots.m +++ b/CanlabCore/@fmri_data/saveplots.m @@ -1,4 +1,48 @@ function saveplots(fmri_dat, varargin) +% saveplots Save the standard fmri_data diagnostic figures to disk as PNGs. +% +% :Usage: +% :: +% +% saveplots(fmri_dat) +% saveplots(fmri_dat, savedir) +% +% Iterates over a registered list of figure-window names produced by the +% standard fmri_data plotting / QC routines (e.g., plot, orthviews, +% montage) and writes each one that currently exists to a PNG file in +% the requested directory. Figures whose registered names don't currently +% exist on screen are silently skipped. +% +% :Inputs: +% +% **fmri_dat:** +% fmri_data object. Currently used only to dispatch the method; +% the figures saved are the ones currently open on screen, not +% regenerated from the object. +% +% **savedir:** *(optional)* +% Output directory. Default: pwd. Created with mkdir if +% it does not already exist. +% +% :Outputs: +% +% None. PNG files are written into savedir with names derived from +% the figure window names (spaces replaced by underscores). +% +% :Registered figure names: +% +% :: +% +% 'fmri data matrix' +% 'Orthviews_means_by_unique_Y' +% 'means by condition (unique Y values)' +% 'Montage_coeff_of_var_across_conditions' +% 'Montage_mean_across_conditions' +% 'Orthviews_fmri_data_mean_and_std' +% +% :See also: +% - plot (creates several of these figures), orthviews, montage +% - scn_export_papersetup (page-setup helper used internally) % Output dir if isempty(varargin) diff --git a/CanlabCore/@fmri_data/spm_coregister.m b/CanlabCore/@fmri_data/spm_coregister.m index 6cead1ab..80b78ba3 100644 --- a/CanlabCore/@fmri_data/spm_coregister.m +++ b/CanlabCore/@fmri_data/spm_coregister.m @@ -1,11 +1,49 @@ function out_obj = spm_coregister(obj, varargin) -% Use SPM to coregister an image to a standard MNI template or another image +% spm_coregister Coregister an fmri_data object to a template using SPM. % -% - writes files to disk using SPM, in same directory as loaded from -% - need reference images on disk; will not work (yet) with objects only -% - also realigns if 4D +% :Usage: +% :: % -% out_obj = spm_coregister(obj, ['template', template_image_name]) +% out_obj = spm_coregister(obj) +% out_obj = spm_coregister(obj, 'template', template_image_name) +% +% Uses SPM's coregistration routines to align the images backing the +% input fmri_data object to a reference template. Writes the resliced +% files to disk in the same directory the source images were loaded +% from (with an r prefix), then loads them back into a new +% fmri_data object. +% +% Behaviour notes: +% +% - Writes files to disk via SPM in the same directory as the source. +% - Requires reference images to exist on disk; will not work (yet) +% with objects whose data is in memory only. +% - For 4-D source images, additionally realigns and reslices the +% remaining frames after coregistering the first volume. +% +% :Inputs: +% +% **obj:** +% fmri_data object whose .fullpath fields point to source image(s) +% on disk. +% +% :Optional Inputs: +% +% **'template', template_image_name:** +% Filename, cellstr, or image_vector specifying the reference +% image to coregister to. Default: the canonical +% icbm152_2009_symm_enhanced_for_underlay underlay shipped +% with CanlabCore. +% +% :Outputs: +% +% **out_obj:** +% fmri_data object loaded from the resliced r-prefixed files +% on disk. +% +% :See also: +% - spm_run_coreg, spm_realign, spm_reslice (SPM-level routines used) +% - resample_space (in-memory resampling alternative) % % ------------------------------------------------------------------------- @@ -84,7 +122,7 @@ % Set options job.eoptions = struct( ... -'cost_fun', 'nmi', ... % 'tol' [112 double], +'cost_fun', 'nmi', ... % 'tol' [1�12 double], 'fwhm', [4 4]); % [7 7] job.roptions = struct( ... diff --git a/CanlabCore/@fmri_data/structure_coefficient_map.m b/CanlabCore/@fmri_data/structure_coefficient_map.m index a53e77d3..43c8c370 100644 --- a/CanlabCore/@fmri_data/structure_coefficient_map.m +++ b/CanlabCore/@fmri_data/structure_coefficient_map.m @@ -1,53 +1,92 @@ function [r1_obj, r2_obj, rdiff_obj] = structure_coefficient_map(obj, pattern1, varargin) -% Calculate structure coefficient map (aka model encoding map) for linear model pattern1 using data in obj +% structure_coefficient_map Voxelwise structure coefficient (model encoding) map for a linear pattern. % -% [r1_obj, r2_obj, rdiff_obj] = structure_coefficient_map(obj, pattern1, varargin) +% :Usage: +% :: % -% pattern1 is an fmri_data object with model weights for a linear multivariate model +% r1_obj = structure_coefficient_map(obj, pattern1) +% [r1_obj, r2_obj, rdiff_obj] = structure_coefficient_map(obj, pattern1, ... +% 'pattern2', pattern2, 'montage', 'nboot', 1000) % -% obj is an fmri_data object with a dataset to calculate structure -% coefficients. For valid statistics, images should be independent (e.g., 1 -% per participant). +% Calculate a structure-coefficient map (aka model encoding map) for the +% linear multivariate model pattern1 using the data in obj. When +% a second pattern is supplied via 'pattern2', also returns its +% structure coefficient map and the difference map between the two. % -% Optional inputs: -% 'pattern2' followed by 2nd model to compare -% 'montage' Plot montage(s) of results -% 'doplots' Plot montage(s) of results -% 'nboot' followed by num bootstrap samples, default 100 (just for -% testing purposes). Recommended: 5000 or more for final analysis +% :Inputs: % -% Example: Single model -% --------------------------------------------------------- -% ncs = load_image_set('ncs'); -% obj = load(which('anic_2021_all_current_v20220831.mat')); -% obj = load_image_set('emotionreg'); -% r1_obj = structure_coefficient_map(obj, ncs); +% **obj:** +% fmri_data object containing the dataset to calculate structure +% coefficients on. For valid statistics, images should be +% independent (e.g., one per participant). % +% **pattern1:** +% fmri_data object containing model weights for a linear +% multivariate model. % -% Example: Comparison of two models -% --------------------------------------------------------- -% nps = load_image_set('nps'); -% vps = load_image_set('vps'); -% obj = load_image_set('emotionreg'); +% :Optional Inputs: % -% pattern1 = nps; pattern2 = vps; -% [ncs_obj, nps_obj, diff_obj] = structure_coefficient_map(obj, ncs, 'pattern2', nps, 'montage', 'nboot', 1000); +% **'pattern2', pattern2:** +% Second fmri_data model to compare against pattern1. Enables +% the difference map output rdiff_obj. % -% % Re-threshold maps and display again: -% r1_obj = threshold(r1_obj, .05, 'unc'); -% rdiff_obj = threshold(rdiff_obj, .05, 'unc'); +% **'montage':** +% Plot montage(s) of results. (Equivalent to 'doplots', 1.) % - -% SEE ALSO -% anic_2021_all_current_v20220831.mat -% get_model_encoding_map.m -% anic_model_encoding_mpa2.m -% prep_3_create_model_encoding_maps.m -% create_model_encoding_maps.m -% method: -% bootstrap_structure_coeff_diff.m +% **'doplots':** +% Plot montage(s) of results. +% +% **'nboot', N:** +% Number of bootstrap samples; default 100 (testing only). +% Recommended: 5000 or more for final analysis. +% +% :Outputs: +% +% **r1_obj:** +% statistic_image for the structure coefficient map of pattern1. +% +% **r2_obj:** +% statistic_image for the structure coefficient map of pattern2 +% (only when 'pattern2' is supplied). +% +% **rdiff_obj:** +% statistic_image for the difference map (pattern1 - pattern2). +% +% :Examples: +% :: +% +% % Single model +% ncs = load_image_set('ncs'); +% obj = load(which('anic_2021_all_current_v20220831.mat')); +% obj = load_image_set('emotionreg'); +% r1_obj = structure_coefficient_map(obj, ncs); +% +% % Comparison of two models +% nps = load_image_set('nps'); +% vps = load_image_set('vps'); +% obj = load_image_set('emotionreg'); +% +% pattern1 = nps; pattern2 = vps; +% [ncs_obj, nps_obj, diff_obj] = structure_coefficient_map( ... +% obj, ncs, 'pattern2', nps, 'montage', 'nboot', 1000); +% +% % Re-threshold maps and display again: +% r1_obj = threshold(r1_obj, .05, 'unc'); +% rdiff_obj = threshold(rdiff_obj, .05, 'unc'); +% +% :See also: +% - get_model_encoding_map +% - bootstrap_structure_coeff_diff (deprecated predecessor) +% - structure_coefficients +% - anic_model_encoding_mpa2, prep_3_create_model_encoding_maps, +% create_model_encoding_maps +% - anic_2021_all_current_v20220831.mat (example dataset) % -% notes structure coeffs source reconstruction.rtfd +% .. +% Programmers' notes: +% See "structure coeffs source reconstruction.rtfd" for derivation +% notes. +% .. % ---------------------------------------------------------------------- % Parse inputs diff --git a/CanlabCore/@fmri_data/structure_coefficients.m b/CanlabCore/@fmri_data/structure_coefficients.m index 76a648d7..ba76d410 100644 --- a/CanlabCore/@fmri_data/structure_coefficients.m +++ b/CanlabCore/@fmri_data/structure_coefficients.m @@ -1,17 +1,47 @@ function sc_img_obj = structure_coefficients(weight_data, src_data_matrix) - % COMPUTE_STRUCTURE_COEFFICIENTS computes structure coefficients for - % an fMRI data object using a given data matrix following Haufe et al., 2014. + % structure_coefficients Compute structure coefficients (Haufe et al., 2014) for an fmri_data weight map. % - % Inputs: - % weight_data - fMRI data object containing weight data the .dat from - % classification e.g., SVM or PCR - % src_data_matrix - data matrix to correlate, usually raw or - % preprocessed data. Not the fmri_data object itself + % :Usage: + % :: % - % Output: - % sc_img_obj - fMRI data object with structure coefficients in sc_img_obj.dat + % sc_img_obj = structure_coefficients(weight_data, src_data_matrix) % - % Michael Sun, Ph.D. + % Computes structure coefficients for an fmri_data object containing + % classifier / regression weights, given a data matrix used to fit + % that model, following Haufe et al. (2014, NeuroImage). The + % structure coefficient at each voxel is the correlation between the + % voxel's data and the model's weighted predicted output, and is + % typically more interpretable than the raw weight. + % + % :Inputs: + % + % **weight_data:** + % fmri_data object whose .dat contains weight maps from a + % classifier or regression model (e.g., SVM, PCR). + % + % **src_data_matrix:** + % Numeric data matrix used to fit the model. Typically raw or + % preprocessed voxel data, not the fmri_data object itself. + % + % :Outputs: + % + % **sc_img_obj:** + % fmri_data object cloned from weight_data, with .dat + % replaced by the corresponding structure coefficients. + % + % :References: + % Haufe, S., Meinecke, F., Goergen, K., Daehne, S., Haynes, J.-D., + % Blankertz, B., & Biessmann, F. (2014). On the interpretation of + % weight vectors of linear models in multivariate neuroimaging. + % NeuroImage, 87, 96-110. + % + % :See also: + % - structure_coefficient_map (bootstrapped voxelwise version) + % - get_model_encoding_map + % + % .. + % Author: Michael Sun, Ph.D. + % .. % Calculate the correlation matrix corr_matrix = corr(src_data_matrix'); diff --git a/CanlabCore/@fmri_data/validate_object.m b/CanlabCore/@fmri_data/validate_object.m index 9c586097..3edfcf29 100644 --- a/CanlabCore/@fmri_data/validate_object.m +++ b/CanlabCore/@fmri_data/validate_object.m @@ -1,44 +1,80 @@ function obj = validate_object(obj) -% validate_object Validate the properties of an fmri_data object. +% validate_object Validate internal invariants of an fmri_data object. % % :Usage: % :: +% +% obj = validate_object(obj) % obj.validate_object() % -% :Description: -% Checks that key properties of the fmri_data object are correctly sized and -% of the correct type. In particular, the method validates: +% Checks that key properties of the fmri_data object are correctly sized +% and of the correct type. Throws an error (id fmri_data:InvalidDimensions) +% if any invariant is violated. Useful as a sanity check after manual +% manipulation of an object's fields, and as an entry point for the test +% suite. +% +% :Inputs: +% +% **obj:** +% An fmri_data (or subclass) object to validate. +% +% :Outputs: % -% - The number of rows in obj.dat equals (obj.volInfo.n_inmask - sum(obj.removed_voxels)). -% - If obj.images_per_session is non-empty, its values sum to (number of columns in obj.dat - sum(obj.removed_images)). -% - obj.removed_voxels is empty, a scalar zero, or a vector of ones/zeros (numeric or logical) -% with length equal to obj.volInfo.n_inmask. -% - obj.removed_images is empty, a scalar zero, or a vector of ones/zeros (numeric or logical). +% **obj:** +% The same object, returned unchanged. The function's effect is +% in raising errors on invariant violations and (optionally) +% printing a confirmation message when obj.verbose is true. % -% Additionally, the volInfo structure is validated: +% :Validation Checks: % -% - volInfo.fname is a non-empty character array. -% - volInfo.dim is a 1×3 integer vector. -% - volInfo.dt is a 1×2 numeric integer vector. -% - volInfo.pinfo is a 3×1 numeric vector. -% - volInfo.mat is a 4×4 numeric matrix. -% - volInfo.n is a 1×2 numeric integer vector. -% - volInfo.descrip is a non-empty character array. -% - volInfo.private is either empty or a 1×1 nifti object. -% - volInfo.nvox is a scalar integer. -% - volInfo.image_indx is a logical column vector with length equal to nvox. -% - volInfo.wh_inmask is a numeric column vector with length equal to n_inmask. -% - volInfo.n_inmask is a scalar integer. -% - volInfo.xyzlist is a numeric matrix of size [n_inmask × 3]. -% - volInfo.cluster is a numeric column vector with length equal to nvox. +% .dat / volInfo / removed_voxels / removed_images: +% - size(obj.dat, 1) == obj.volInfo.n_inmask - sum(obj.removed_voxels). +% - If obj.images_per_session is non-empty, its values sum to +% (size(obj.dat, 2) - sum(obj.removed_images)). +% - obj.removed_voxels is empty, a scalar zero, or a vector of +% ones/zeros (numeric or logical) with length equal to +% obj.volInfo.n_inmask. +% - obj.removed_images is empty, a scalar zero, or a vector of +% ones/zeros (numeric or logical). % -% :Example: +% volInfo structure fields: +% - volInfo.fname: non-empty character array. +% - volInfo.dim: 1x3 integer vector. +% - volInfo.dt: 1x2 numeric integer vector. +% - volInfo.pinfo: 3x1 numeric vector. +% - volInfo.mat: 4x4 numeric matrix. +% - volInfo.n: 1x2 numeric integer vector. +% - volInfo.descrip: non-empty character array. +% - volInfo.private: empty or 1x1 nifti object. +% - volInfo.nvox: scalar integer. +% - volInfo.image_indx: logical column vector, length = nvox. +% - volInfo.wh_inmask: numeric column vector, length = n_inmask. +% - volInfo.n_inmask: scalar integer. +% - volInfo.xyzlist: numeric matrix of size [n_inmask x 3]. +% - volInfo.cluster: numeric column vector, length = n_inmask. +% +% image_metadata fields: +% - Logical flag fields (is_timeseries, is_single_trial_series, +% is_first_level_maps, is_MNI_space, is_HP_filtered, +% covariates_removed) must be logical scalars when not NaN. +% - Numeric fields (TR_in_sec, HP_filter_cutoff_sec) must be +% numeric scalars (NaN is acceptable). +% +% :Examples: % :: +% % fmri_obj.validate_object(); +% fmri_obj = validate_object(fmri_obj); +% +% :See also: +% - replace_empty, remove_empty (state changes that interact with the +% dimensional invariants validated here) % -% Author: Your Name -% Date: YYYY-MM-DD -% License: GNU General Public License v3 or later +% .. +% Programmers' notes: +% Throws fmri_data:InvalidDimensions on size mismatches; relies on +% validateattributes() for type/shape checks elsewhere. +% .. % ------------------------------------------------------------------------- % Check specific sizes of variables that must add up diff --git a/CanlabCore/@fmri_data/windsorize.m b/CanlabCore/@fmri_data/windsorize.m index 8fcf97ab..000af159 100644 --- a/CanlabCore/@fmri_data/windsorize.m +++ b/CanlabCore/@fmri_data/windsorize.m @@ -1,16 +1,49 @@ function obj = windsorize(obj, varargin) -% Windsorize an fMRI data object to madlimit Median Absolute Deviations. -% Default = 5 MADs. -% Works across rows and columns. -% Registers this step in history. +% windsorize Clip extreme values in an fmri_data object to a MAD-based limit. % % :Usage: % :: % -% obj = windsorize(obj, [madlimit]) - +% obj = windsorize(obj) +% obj = windsorize(obj, madlimit) +% +% Replaces any value in obj.dat that lies further than madlimit +% Median Absolute Deviations from the global median with the +% corresponding cap value. Operates across the entire .dat matrix +% (i.e., across both rows/voxels and columns/images jointly). Prints +% a descriptives summary and the count of outlier values, and +% appends an entry to obj.history recording the MAD limit used. +% +% :Inputs: +% +% **obj:** +% fmri_data object whose .dat will be windsorized. +% +% **madlimit:** *(optional)* +% Numeric scalar; values farther than madlimit MADs from the +% global median are clipped to median +/- madlimit*MAD. +% Default: 5. +% +% :Outputs: +% +% **obj:** +% fmri_data object with .dat windsorized in place. Caps and +% outlier counts are printed to the command window. The +% operation is appended to obj.history as +% 'dat windsorized to MADs'. +% +% :Examples: +% :: +% +% obj = canlab_get_sample_fmri_data(); +% obj = windsorize(obj); % default 5 MADs +% obj = windsorize(obj, 3); % more aggressive clipping +% +% :See also: +% - rescale, preprocess (other obj-wide value transforms) +% % .. -% Calculate and display descriptives +% Calculate and display descriptives, then clip in place. % .. madlimit = 5; diff --git a/CanlabCore/@fmri_timeseries/fmri_timeseries.m b/CanlabCore/@fmri_timeseries/fmri_timeseries.m index 1d4203d4..7b36a2f1 100644 --- a/CanlabCore/@fmri_timeseries/fmri_timeseries.m +++ b/CanlabCore/@fmri_timeseries/fmri_timeseries.m @@ -532,16 +532,12 @@ % Now extract the actual data from the mask switch spm('Ver') - - case {'SPM12','SPM8', 'SPM5'} - imgdat = iimg_get_data(maskobj.volInfo, image_names, 'single', verbosestr, 'noexpand'); - case {'SPM2', 'SPM99'} - % legacy, for old SPM + % legacy SPM imgdat = iimg_get_data(maskobj.volInfo, image_names, 'single', verbosestr); - otherwise - error('Unknown version of SPM! Update code, check path, etc.'); + % SPM5+, including any future versions + imgdat = iimg_get_data(maskobj.volInfo, image_names, 'single', verbosestr, 'noexpand'); end imgdat = imgdat'; diff --git a/CanlabCore/@fmridisplay/activate_figures.m b/CanlabCore/@fmridisplay/activate_figures.m index 4900dd17..68c170c2 100644 --- a/CanlabCore/@fmridisplay/activate_figures.m +++ b/CanlabCore/@fmridisplay/activate_figures.m @@ -1,19 +1,52 @@ function [figure_handles, figure_numbers, all_axis_han, is_valid_handle] = activate_figures(o2, varargin) -% Activate all figures associated with an fmridisplay object, or a subset for specific montage(s) +% activate_figures Activate figures associated with an fmridisplay object. % -% Usage: -% [all_fig_han, all_axis_han, is_valid_handle] = activate_figures(o2, wh_montages) +% Activate all figures associated with an fmridisplay object, or a subset +% for specific montage(s). % -% Inputs: -% o2 % an fmridisplay object with montages attached -% Outputs: -% figure_handles = {}; % cell array of handles for unique figures associated with fmridisplay object -% figure_numbers = []; % vector of figure numbers for unique figures associated with fmridisplay object -% all_axis_han = []; % vector of all axis handles associated with montages/figure elements -% is_valid_handle = []; % vector of which handles are valid (not deleted) +% :Usage: +% :: % -% Limitations: -% No support for surfaces yet; montages only +% [figure_handles, figure_numbers, all_axis_han, is_valid_handle] = activate_figures(o2) +% [figure_handles, figure_numbers, all_axis_han, is_valid_handle] = activate_figures(o2, wh_montages) +% +% :Inputs: +% +% **o2:** +% An fmridisplay object with montages attached. +% +% :Optional Inputs: +% +% **wh_montages:** +% Vector of montage indices to activate. Default: all montages +% (1:length(o2.montage)). A warning is issued and the function +% returns early if any requested index exceeds the number of +% montages. +% +% :Outputs: +% +% **figure_handles:** +% Cell array of handles for unique figures associated with the +% fmridisplay object. +% +% **figure_numbers:** +% Vector of figure numbers for unique figures associated with +% the fmridisplay object. +% +% **all_axis_han:** +% Vector of all axis handles associated with montages/figure +% elements. +% +% **is_valid_handle:** +% Vector of which handles are valid (not deleted). +% +% :Limitations: +% +% No support for surfaces yet; montages only. +% +% :See also: +% - fmridisplay +% - montage % Initialize outputs % ---------------------------------------------------- diff --git a/CanlabCore/@fmridisplay/addpoints.m b/CanlabCore/@fmridisplay/addpoints.m index 496be742..ea96cc7c 100644 --- a/CanlabCore/@fmridisplay/addpoints.m +++ b/CanlabCore/@fmridisplay/addpoints.m @@ -1,12 +1,8 @@ function obj = addpoints(obj, xyz, varargin) -% Plots points on fmridisplay objects (e.g., montages of slices) +% addpoints Plot points (coordinate locations) on fmridisplay montages. % -% :Usage: -% :: -% -% newax = addpoints(obj, xyz, varargin) -% -% Registers handles with the object (referred to as obj) +% Plots points on fmridisplay objects (e.g., montages of slices) and +% registers their graphics handles with the object (referred to as obj). % % - enter xyz as n x 3 list of coordinates in mm to plot (world space) % - Points or text labels or both @@ -14,39 +10,70 @@ % - axial, saggital, or coronal orientation handled automatically % - Multiple different sets of points can be plotted in different colors/text labels % +% :Usage: +% :: +% +% obj = addpoints(obj, xyz, varargin) +% +% :Inputs: +% +% **obj:** +% An fmridisplay object with one or more montages attached. +% +% **xyz:** +% n x 3 list of coordinates in mm to plot (world space). +% % :Optional Inputs: -% -% Takes all inputs of plot_points_on_slice. See help for additional -% documentation of options. +% +% Takes all inputs of plot_points_on_slice. See help for additional +% documentation of options. % % **{'text', 'textcodes'}:** -% cell array of text values corresponding to points +% cell array of text values corresponding to points. % % **{'condf' 'colorcond'}:** -% vector of integers to define color conditions +% vector of integers to define color conditions. % % **'close_enough':** -% mm within which to plot; defined automatically based on slice distance if not entered +% mm within which to plot; defined automatically based on slice +% distance if not entered. % % **'color':** -% string, 'b', or vector, [1 0 0], to define colors; cell if condf is used, e.g., {'b' 'g'} +% string, 'b', or vector, [1 0 0], to define colors; cell if +% condf is used, e.g., {'b' 'g'}. % % **{'marker', 'MarkerStyle'}:** -% e.g., 'o', 'v', 's' +% e.g., 'o', 'v', 's'. % % **{'MarkerSize', 'markersize'}:** +% Marker size (numeric). % % **{'MarkerFaceColor', 'markerfacecolor'}:** -% see color above +% see color above. % -% :Examples: +% **{'wh_montages', 'wh_montage', 'which_montages', 'which montages'}:** +% Followed by a vector of montage indices to plot to. Default: +% all montages. % -% Plot points (i.e., coordinate locations) for xyz coords: +% :Outputs: +% +% **obj:** +% The input fmridisplay object with new graphics handles +% appended to obj.montage{i}.plotted_point_handles for each +% affected montage. +% +% :Examples: % :: % -% o2 = addpoints(o2, DB.xyz, 'MarkerFaceColor', 'b', 'Marker', 'o', 'MarkerSize', 4); -% o2 = addpoints(o2, DB.xyz, 'text', DB.textcodes, 'condf', DB.condf, 'color', {'b' 'g'}); -% o2 = removepoints(o2); +% % Plot points (i.e., coordinate locations) for xyz coords: +% o2 = addpoints(o2, DB.xyz, 'MarkerFaceColor', 'b', 'Marker', 'o', 'MarkerSize', 4); +% o2 = addpoints(o2, DB.xyz, 'text', DB.textcodes, 'condf', DB.condf, 'color', {'b' 'g'}); +% o2 = removepoints(o2); +% +% :See also: +% - fmridisplay +% - removepoints +% - plot_points_on_slice wh_montage = 1:length(obj.montage); % select which montages; default = all diff --git a/CanlabCore/@fmridisplay/fmridisplay.m b/CanlabCore/@fmridisplay/fmridisplay.m index 94514c11..0640e4f3 100644 --- a/CanlabCore/@fmridisplay/fmridisplay.m +++ b/CanlabCore/@fmridisplay/fmridisplay.m @@ -1,95 +1,128 @@ -% fmridisplay: Data class for storing data matrices and information +% fmridisplay Data class for underlay + activation map montages and plots. % % 'fmridisplay' is a data class containing information about an underlay % and activation map(s) for creating montage plots and other types of % plots. % -% The default brain for overlays is MNI152NLin2009cAsym +% The default brain for overlays is MNI152NLin2009cAsym. % For legacy SPM8 single subject, enter as arguments: % 'overlay', which('SPM8_colin27T1_seg.img') % For Keuken 2014 (used prior to 2024) enter: % 'overlay', which('keuken_2014_enhanced_for_underlay.img') % -% Creating class instances -% ----------------------------------------------------------------------- -% -% -% Examples -% ----------------------------------------------------------------------- -% obj = fmridisplay; Create object with canonical underlay -% obj = montage(obj); Show axial montage of underlay -% obj = addblobs(obj, cl); Add blobs from cl clusters -% -% obj = fmridisplay('montage', 'addblobs', cl); Do all of the above -% -% Add colored green blobs, with smoothed edges -% obj = addblobs(obj, cl, 'trans', 'color', [0 1 0], 'smooth'); -% -% Create a second montage with 4 mm spacing and add blue blobs to both: -% obj = montage(obj, 'spacing', 4); -% obj = addblobs(obj, cl, 'trans', 'color', [0 0 1], 'smooth'); -% -% Add axial and parasaggital montages to the same figure: -% o2 = montage(o2, 'axial', 'slice_range', [-40 50], 'onerow', 'spacing', 6); -% axh = axes('Position', [0.03 0.45 .1 .5]); -% o2 = montage(o2, 'saggital', 'wh_slice', [0 0 0], 'existing_axes', axh); -% -% Add blue outlines only: -% obj = addblobs(obj, cl, 'outline', 'color', [0 0 1]); -% -% Sagittal images and blobs: -% o2 = fmridisplay; -% o2 = montage(o2, 'saggital', 'slice_range', [-20 20], 'onerow'); -% o2 = addblobs(o2, cl); -% legend(o2, 'figure') -% o2 = addblobs(o2, cl, 'contour', 'color', [0 1 0]); -% -% Display over your custom anatomical image: -% overlay = 'mean_T1_FSL1.nii'; -% o2 = fmridisplay('overlay', overlay); -% -% Overlapping sagittal and axial images with outlines -% o2 = fmridisplay; -% o2 = montage(o2, 'saggital', 'slice_range', [-10 10], 'onerow'); -% enlarge_axes(gcf, 1.2); -% o2 = montage(o2, 'axial', 'slice_range', [-40 50], 'onerow', 'spacing', 4); -% o2 = addblobs(o2, cl, 'splitcolor', {[0 0 1] [.3 0 .8] [.8 .3 0] [1 1 0]}); -% o2 = addblobs(o2, cl, 'maxcolor', [1 0 0], 'mincolor', [1 .3 0], 'cmaprange', [0 .01], 'outline'); -% -% Add transparent blobs, either mapped with color scale or constant -% opacity value: -% o2 = addblobs(o2, cl, 'splitcolor', {[0 0 1] [0 1 1] [1 .5 0] [1 1 0]}, 'cmaprange', [-2 2], 'trans'); -% o2 = addblobs(o2, cl, 'splitcolor', {[0 0 1] [0 1 1] [1 .5 0] [1 1 0]}, 'cmaprange', [-2 2], 'transvalue', .85); -% -% o2 = montage(o2, 'saggital', 'slice_range', [-6 6], 'onerow'); -% o2 = montage(o2, 'axial', 'slice_range', [-20 30], 'onerow', 'spacing', 8); -% xyz = a list of [x y z] coordinates, one coord per row -% o2 = montage(o2, 'axial', 'wh_slice', xyz, 'onerow'); -% squeeze_axes(o2.montage{1}.axis_handles, 20) -% squeeze_axes(o2.montage{2}.axis_handles, 10) -% -% Plot points (i.e., coordinate locations) rather than blobs: -% o2 = addpoints(o2, DB.xyz, 'MarkerFaceColor', 'b', 'Marker', 'o', 'MarkerSize', 4); -% o2 = addpoints(o2, DB.xyz, 'text', DB.textcodes, 'condf', DB.condf, 'color', {'b' 'g'}); -% o2 = removepoints(o2); -% -% Add to only 2nd montage in registered vector of montages in obj.montage -% obj = addblobs(obj, cl, 'which_montages', 2); -% -% Make a montage with centers of each significant cluster and add blobs to that: -% r = region(b2); -% xyz = cat(1, r.mm_center) -% o2 = montage(o2, 'axial', 'wh_slice', xyz, 'onerow'); -% o2 = addblobs(o2, region(b2)); -% -% Methods -% ----------------------------------------------------------------------- -% See methods(fmridisplay) -% -% See fmridisplay.montage for list of all slice display options -% See fmridisplay.render_blobs for list of all rendering options -% -% Copyright 2011 - Tor Wager +% :Usage: +% :: +% +% obj = fmridisplay; +% obj = fmridisplay('overlay', overlay_filename); +% obj = fmridisplay('montage', 'addblobs', cl); +% +% :Optional Inputs: +% +% **'overlay':** +% Followed by a filename for the underlay image. Default: +% which('fmriprep20_template.nii.gz'). +% +% **'montage':** +% If present, calls montage() on the constructed object and +% passes through remaining varargin. +% +% **'addblobs':** +% Followed by a clusters/region structure to add as blobs. +% +% :Outputs: +% +% **obj:** +% An fmridisplay object with .overlay, .SPACE, +% .activation_maps, .montage, .surface, .orthviews, .history, +% and .additional_info fields populated. +% +% :Examples: +% :: +% +% obj = fmridisplay; % Create object with canonical underlay +% obj = montage(obj); % Show axial montage of underlay +% obj = addblobs(obj, cl); % Add blobs from cl clusters +% +% obj = fmridisplay('montage', 'addblobs', cl); % Do all of the above +% +% % Add colored green blobs, with smoothed edges +% obj = addblobs(obj, cl, 'trans', 'color', [0 1 0], 'smooth'); +% +% % Create a second montage with 4 mm spacing and add blue blobs to both: +% obj = montage(obj, 'spacing', 4); +% obj = addblobs(obj, cl, 'trans', 'color', [0 0 1], 'smooth'); +% +% % Add axial and parasaggital montages to the same figure: +% o2 = montage(o2, 'axial', 'slice_range', [-40 50], 'onerow', 'spacing', 6); +% axh = axes('Position', [0.03 0.45 .1 .5]); +% o2 = montage(o2, 'saggital', 'wh_slice', [0 0 0], 'existing_axes', axh); +% +% % Add blue outlines only: +% obj = addblobs(obj, cl, 'outline', 'color', [0 0 1]); +% +% % Sagittal images and blobs: +% o2 = fmridisplay; +% o2 = montage(o2, 'saggital', 'slice_range', [-20 20], 'onerow'); +% o2 = addblobs(o2, cl); +% legend(o2, 'figure') +% o2 = addblobs(o2, cl, 'contour', 'color', [0 1 0]); +% +% % Display over your custom anatomical image: +% overlay = 'mean_T1_FSL1.nii'; +% o2 = fmridisplay('overlay', overlay); +% +% % Overlapping sagittal and axial images with outlines +% o2 = fmridisplay; +% o2 = montage(o2, 'saggital', 'slice_range', [-10 10], 'onerow'); +% enlarge_axes(gcf, 1.2); +% o2 = montage(o2, 'axial', 'slice_range', [-40 50], 'onerow', 'spacing', 4); +% o2 = addblobs(o2, cl, 'splitcolor', {[0 0 1] [.3 0 .8] [.8 .3 0] [1 1 0]}); +% o2 = addblobs(o2, cl, 'maxcolor', [1 0 0], 'mincolor', [1 .3 0], 'cmaprange', [0 .01], 'outline'); +% +% % Add transparent blobs, either mapped with color scale or constant +% % opacity value: +% o2 = addblobs(o2, cl, 'splitcolor', {[0 0 1] [0 1 1] [1 .5 0] [1 1 0]}, 'cmaprange', [-2 2], 'trans'); +% o2 = addblobs(o2, cl, 'splitcolor', {[0 0 1] [0 1 1] [1 .5 0] [1 1 0]}, 'cmaprange', [-2 2], 'transvalue', .85); +% +% o2 = montage(o2, 'saggital', 'slice_range', [-6 6], 'onerow'); +% o2 = montage(o2, 'axial', 'slice_range', [-20 30], 'onerow', 'spacing', 8); +% % xyz = a list of [x y z] coordinates, one coord per row +% o2 = montage(o2, 'axial', 'wh_slice', xyz, 'onerow'); +% squeeze_axes(o2.montage{1}.axis_handles, 20) +% squeeze_axes(o2.montage{2}.axis_handles, 10) +% +% % Plot points (i.e., coordinate locations) rather than blobs: +% o2 = addpoints(o2, DB.xyz, 'MarkerFaceColor', 'b', 'Marker', 'o', 'MarkerSize', 4); +% o2 = addpoints(o2, DB.xyz, 'text', DB.textcodes, 'condf', DB.condf, 'color', {'b' 'g'}); +% o2 = removepoints(o2); +% +% % Add to only 2nd montage in registered vector of montages in obj.montage +% obj = addblobs(obj, cl, 'which_montages', 2); +% +% % Make a montage with centers of each significant cluster and add blobs to that: +% r = region(b2); +% xyz = cat(1, r.mm_center) +% o2 = montage(o2, 'axial', 'wh_slice', xyz, 'onerow'); +% o2 = addblobs(o2, region(b2)); +% +% :Methods: +% +% See methods(fmridisplay) +% See fmridisplay.montage for list of all slice display options +% See fmridisplay.render_blobs for list of all rendering options +% +% :See also: +% - addblobs +% - addpoints +% - removeblobs +% - removepoints +% - legend +% - title_montage +% +% .. +% Copyright 2011 - Tor Wager +% .. classdef fmridisplay diff --git a/CanlabCore/@fmridisplay/legend.m b/CanlabCore/@fmridisplay/legend.m index 92f8347e..e152e180 100644 --- a/CanlabCore/@fmridisplay/legend.m +++ b/CanlabCore/@fmridisplay/legend.m @@ -1,26 +1,64 @@ function obj = legend(obj, varargin) -% Creates legend for fmridisplay object -% Adds legend axis handles to obj.activation_maps{:} +% legend Create a colorbar legend for an fmridisplay object. +% +% Creates legend(s) for an fmridisplay object and adds legend axis +% handles to obj.activation_maps{:}. +% +% Notes: scaleanchors is min and max values for pos and neg range (for +% splitmap). % % :Usage: % :: % % obj = legend(obj, varargin) +% obj = legend(obj, 'figure') % new figure +% +% :Inputs: +% +% **obj:** +% An fmridisplay object with one or more activation_maps +% attached. +% +% :Optional Inputs: +% +% **{'figure', 'newfig'}:** +% Plot legends in a new figure with larger panels and font. +% Default: plot small legends in the current figure. +% +% **'noverbose':** +% Suppress informational messages (e.g., 'No variability...'). +% +% **'indexmap':** +% Followed by a colormap (cmap) for an indexed/parcellation +% legend (a single colorbar with one tick per label). +% +% **'labels':** +% Followed by a cell array of label strings. Only used together +% with 'indexmap'; otherwise a warning is issued. % -% obj = legend(obj, 'figure') % new figure +% :Outputs: +% +% **obj:** +% The fmridisplay object with .activation_maps{c}.legendhandle +% set to the legend axis handle for each activation map. +% +% :Examples: +% :: +% +% o2 = legend(o2); % small legends in current figure +% o2 = legend(o2, 'figure'); % large legends in a new figure +% +% :See also: +% - fmridisplay +% - addblobs % % .. % Tor Wager % 8/17/2016 - pkragel updated to accomodate split colormap % % Michael Sun -% 07/29/2024 - Updated to allowfor indexmap labelling - +% 07/29/2024 - Updated to allow for indexmap labelling % .. -% -% Notes: scaleanchors is min and max values for pos and neg range (for -% splitmap) -% doverbose = true; donewfig = false; diff --git a/CanlabCore/@fmridisplay/removeblobs.m b/CanlabCore/@fmridisplay/removeblobs.m index f6579628..de797fb6 100644 --- a/CanlabCore/@fmridisplay/removeblobs.m +++ b/CanlabCore/@fmridisplay/removeblobs.m @@ -1,4 +1,34 @@ function obj = removeblobs(obj) +% removeblobs Remove all rendered blobs from an fmridisplay object. +% +% Deletes blob graphics handles (and any associated legend handles) from +% every entry in obj.activation_maps, removes those entries from the +% object, and erases blobs from any registered surface objects via +% addbrain('eraseblobs', ...). +% +% :Usage: +% :: +% +% obj = removeblobs(obj) +% +% :Inputs: +% +% **obj:** +% An fmridisplay object with one or more activation_maps and/or +% surfaces attached. +% +% :Outputs: +% +% **obj:** +% The same fmridisplay object with deleted graphics handles and +% with cleared entries removed from .activation_maps. Surfaces +% in .surface are kept but have their blob colorings erased. +% +% :See also: +% - fmridisplay +% - addblobs +% - removepoints +% - addbrain to_remove = []; diff --git a/CanlabCore/@fmridisplay/removepoints.m b/CanlabCore/@fmridisplay/removepoints.m index 5b344769..14654d60 100644 --- a/CanlabCore/@fmridisplay/removepoints.m +++ b/CanlabCore/@fmridisplay/removepoints.m @@ -1,4 +1,31 @@ function obj = removepoints(obj) +% removepoints Delete plotted point handles from all montages in an fmridisplay. +% +% Iterates over each montage in obj.montage and deletes any valid +% graphics handles stored in the .plotted_point_handles field +% (created by addpoints). +% +% :Usage: +% :: +% +% obj = removepoints(obj) +% +% :Inputs: +% +% **obj:** +% An fmridisplay object with points previously added via +% addpoints. +% +% :Outputs: +% +% **obj:** +% The same fmridisplay object with .plotted_point_handles +% deleted from each montage. +% +% :See also: +% - fmridisplay +% - addpoints +% - removeblobs for i = 1:length(obj.montage) diff --git a/CanlabCore/@fmridisplay/title_montage.m b/CanlabCore/@fmridisplay/title_montage.m index 43f7344e..e5142daa 100644 --- a/CanlabCore/@fmridisplay/title_montage.m +++ b/CanlabCore/@fmridisplay/title_montage.m @@ -1,10 +1,40 @@ function [o2, title_handle] = title_montage(o2, wh_montage, title_str) -% Add a title to a montage figure registered in an fmridisplay object o2 +% title_montage Add a title to a montage registered in an fmridisplay object. % -% [o2, title_handle] = title_montage(o2, wh_montage, title_str) +% Adds a title to a montage figure registered in an fmridisplay object o2. +% Underscores in title_str are replaced with spaces before display. % -% wh_montage is an integer that specifies the image axis to add the title to -% it is usually 5 for standard canlab 2020 double-row montages. +% :Usage: +% :: +% +% [o2, title_handle] = title_montage(o2, wh_montage, title_str) +% +% :Inputs: +% +% **o2:** +% An fmridisplay object with one or more montages attached. +% +% **wh_montage:** +% An integer that specifies the image axis (montage index) to +% add the title to. It is usually 5 for standard canlab 2020 +% double-row montages. +% +% **title_str:** +% Title string to display. Underscores are replaced with spaces. +% +% :Outputs: +% +% **o2:** +% The fmridisplay object, with .montage{wh_montage}.title set +% to title_str. +% +% **title_handle:** +% Handle to the title graphics object, or [] if the target axis +% handle was invalid (e.g., figure was closed). +% +% :See also: +% - fmridisplay +% - montage title_handle = []; diff --git a/CanlabCore/@fmridisplay/transparency_change.m b/CanlabCore/@fmridisplay/transparency_change.m index feb5e4f3..bfff1656 100644 --- a/CanlabCore/@fmridisplay/transparency_change.m +++ b/CanlabCore/@fmridisplay/transparency_change.m @@ -1,12 +1,33 @@ function transparency_change(o2, multval) -% Change the transparency of blobs in an fmridisplay object +% transparency_change Scale the transparency of all blobs in an fmridisplay object. +% +% Multiplies the AlphaData of every blob graphics handle in +% o2.activation_maps by multval, clipping any resulting values above 1. +% Values < 1 make blobs more transparent, > 1 make blobs more opaque. +% +% :Usage: +% :: +% +% transparency_change(o2, multval) % % :Inputs: % +% **o2:** +% An fmridisplay object with one or more activation_maps +% containing blobhandles. +% % **multval:** -% multiply transparency values by this. +% Scalar to multiply transparency values by. Values < 1 make +% blobs more transparent, > 1 make blobs more opaque. +% +% :Outputs: +% +% None. Blob graphics objects in o2 are modified in place. % -% values < 1 makes blobs more transparent, > 1 makes blobs more opaque +% :See also: +% - fmridisplay +% - addblobs +% - removeblobs for m = 1:length(o2.activation_maps) diff --git a/CanlabCore/@fmridisplay/zoom_in_on_regions.m b/CanlabCore/@fmridisplay/zoom_in_on_regions.m index 7c3f9a53..38d0de1e 100644 --- a/CanlabCore/@fmridisplay/zoom_in_on_regions.m +++ b/CanlabCore/@fmridisplay/zoom_in_on_regions.m @@ -1,16 +1,50 @@ function zoom_in_on_regions(o2, cl, orientation) +% zoom_in_on_regions Zoom each per-region axis in an fmridisplay onto its cluster. +% % Assumes you have one axis per region in a montage registered in an % fmridisplay object (o2). Given the object o2, which montage to adjust % (montage_num), a corresponding region object with coordinates (cl), and an -% orientation ('axial', 'saggital', or 'coronal'), this object method zooms -% in on each cluster by adjusting the axes. +% orientation ('axial', 'saggital', or 'coronal'), this object method zooms +% in on each cluster by adjusting the axes. +% +% :Usage: +% :: +% +% zoom_in_on_regions(o2, cl, orientation) +% +% :Inputs: +% +% **o2:** +% An fmridisplay object with one axis per region in its +% .montage entries. +% +% **cl:** +% A region object (or compatible struct) with .XYZmm +% coordinates per region. +% +% **orientation:** +% Slice orientation, one of 'axial', 'saggital', or 'coronal'. +% Determines which two coordinate axes are used to set XLim/YLim. +% +% :Outputs: +% +% None. The XLim/YLim of each montage axis are modified in place. +% +% :Examples: +% :: +% +% thal = load_atlas('thalamus'); +% o2 = montage(thal, 'nosymmetric', 'regioncenters'); +% zoom_in_on_regions(o2, 1, atlas2region(thal), 'axial'); % -% Tor Wager, Feb 2018 +% :See also: +% - fmridisplay +% - region +% - montage % -% Examples: -% thal = load_atlas('thalamus'); -% o2 = montage(thal, 'nosymmetric', 'regioncenters'); -% zoom_in_on_regions(o2, 1, atlas2region(thal), 'axial'); +% .. +% Tor Wager, Feb 2018 +% .. % Zoom in on regions! diff --git a/CanlabCore/@image_vector/apply_atlas.m b/CanlabCore/@image_vector/apply_atlas.m deleted file mode 100644 index ab5caf5c..00000000 --- a/CanlabCore/@image_vector/apply_atlas.m +++ /dev/null @@ -1,314 +0,0 @@ -function [parcel_means, parcel_pattern_expression] = apply_atlas(dat, parcels, varargin) -% Computes the mean value or pattern expression for each reference region specified in an atlas object -% -% Usage: -% :: -% -% [parcel_means, parcel_pattern_expression] = apply_atlas(dat,parcels,'pattern_expression',fmri_data('pattern.nii')) -% -% This is a method for an fmri_data object that computes the mean value and -% optionally, pattern expression within parcels. This can be used to -% compare the average activity within a region to the expression of a -% particular marker -% -% .. -% Author and copyright information: -% -% Copyright (C) 2017 Phil Kragel -% -% This program is free software: you can redistribute it and/or modify -% it under the terms of the GNU General Public License as published by -% the Free Software Foundation, either version 3 of the License, or -% (at your option) any later version. -% -% This program is distributed in the hope that it will be useful, -% but WITHOUT ANY WARRANTY; without even the implied warranty of -% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -% GNU General Public License for more details. -% -% You should have received a copy of the GNU General Public License -% along with this program. If not, see . -% .. -% -% :Inputs: -% -% **dat:** -% fmri_data object with data to analyze -% -% **parcels:** -% fmri_data atlas object with parcellation image, with parcel ID coded -% with unique integer values in the image -% -% :Optional Inputs: -% -% **pattern_expression:** -% followed by an fmri_data object with multivariate pattern to apply. -% local patterns in each parcel are applied, and pattern responses -% returned for each parcel. -% -% **correlation:** -% calculate the pearson correlation coefficient of each -% image in dat and the values in the mask. -% -% **norm_mask:** -% normalize the mask weights by L2 norm -% -% **ignore_missing:** -% used with pattern expression only. Ignore weights on voxels -% with zero values in test image. If this is not entered, the function will -% check for these values and give a warning. -% -% **cosine_similarity:** -% used with pattern expression only. scales expression by product of -% l2 norms (norm(mask)*norm(dat)) -% -% :Examples: -% :: -% -% % Load the Shen 2013 NIMG parcellation and a sample dataset, and get the -% means for each parcel in each image in the dataset. -% -% parcel_image = which('shen_2mm_268_parcellation.nii'); -% parcel_obj = fmri_data(parcel_image); -% dat = load_image_set('emotionreg'); -% parcel_means = apply_parcellation(dat, parcel_obj); -% -% % Calculate the local NPS pattern response in each region within the Shen -% atlas. This requires the pattern images to be on your path! -% nps = load_image_set('npsplus'); -% nps = get_wh_image(nps, 1); -% [parcel_means, local_pattern_response] = apply_parcellation(dat, parcel_obj, 'pattern_expression', nps); -% -% Parcels without pattern values will be NaN. Visualize in-pattern -% parcels: -% r = region(parcel_obj, 'unique_mask_values'); -% wh_parcels = ~all(isnan(parcel_means)); -% orthviews(r(wh_parcels)); -% -% :See also: -% -% -% .. -% Notes: -% Created: 5/12/17 Phil Kragel - bits of code taken from apply_mask -% 5/13/17 Phil and Tor revise; minor edits by Tor Wager -% -% .. - -%error('apply_atlas is deprecated. Use apply_parcellation method for atlas objects instead. see also brainpathway object') -warning('apply_atlas is deprecated. Use apply_parcellation method for atlas objects instead. see also brainpathway object') - -[parcel_means, parcel_pattern_expression] = deal([]); - -dopatternexpression = 0; - -if isa(parcels, 'atlas') - - parcels.probability_maps = []; % speed up resampling dramatically. not needed here. - -end - -% Check parcels -% Get integer vector of unique parcel IDs, before any resampling. -% ------------------------------------------------------------------------- -if size(parcels.dat, 2) > 1 - error('Sorry, this function currently only works for one parcel image at a time!') -end - -u = unique(parcels.dat); -u(u == 0) = []; -if ~all(u == round(u)) - warning('Some parcel values are not integers.'); -end - -% get pattern_image data object if specified -if any(strcmp(varargin, 'pattern_expression')) - - pattern_image = varargin{find(strcmp(varargin, 'pattern_expression')) + 1}; - dopatternexpression = true; - - % resample pattern_image to data space - [dat, pattern_image] = match_spaces(dat, pattern_image); - - if size(pattern_image.dat, 2) > 1 - error('Sorry, this function currently only works for one pattern at a time!') - end - -end - -[dat,parcels] = match_spaces(dat, parcels); % keeps only in-image parcels - -% Turn integer vector into 1s and 0s - matrix of binary masks -parcels = remove_empty(parcels); -[parcels.dat, parcels_we_have] = condf2indic(parcels.dat); - -% if codes are 1...n, parcels_we_have is index values...but if there are -% missing integers, need to re-map them into indices -index_values = 1:length(u); -[~, wh] = intersect(u, parcels_we_have); -index_values = index_values(wh); - -% Insert NaNs for any missing parcels. Happens rarely, but could happen. -missing_parcels = true(size(u)); -missing_parcels(index_values) = false; - -parcels.dat = naninsert(missing_parcels, parcels.dat')'; - -%for computing means, scale each column of parcels to sum to 1 -parcels.dat = bsxfun(@rdivide, parcels.dat, sum(parcels.dat)); - -%matrix products will give us the mean now... -parcel_means = dat.dat' * parcels.dat; - - -if dopatternexpression - - %go back to binary values for parcels - parcels.dat = single(parcels.dat > 0); - - parcels = replace_empty(parcels); - pattern_image = replace_empty(pattern_image); - - %expanded_mask = mask.dat; % would do for full set all at once, no - %loop...debug - - %expand pattern_image.dat to provide output for each parcel - parcel_pattern_expression = NaN .* zeros(size(parcel_means)); - - % **** initialize dat matrix first, then fill in values for all - % parcels ****** - - % for each parcel... - - mask_by_parcels=nan(size(pattern_image.dat,1),size(parcels.dat,2)); - - for i = 1:size(parcels.dat, 2) - - % skip if this parcel is empty - if all(isnan(parcel_means(:, i))) - continue - end - - - mask_by_parcels(:, i) = pattern_image.dat(:, 1) .* parcels.dat(:, i); % zero out-of-parcel voxels - - end - - % *****call canlab_pattern_similarity once, which loops through pattern - % masks and datasets and considers non-empty values in data images - - % Pass similarity metric in to canlab_pattern_similarity - dat=replace_empty(dat); - parcel_pattern_expression = canlab_pattern_similarity(dat.dat, mask_by_parcels, 'ignore_missing', varargin{:}); - - % similarity_output = canlab_pattern_similarity(dat.dat, weights, 'ignore_missing', varargin{:}); - - %tried doing this all at once, but apply_mask has issues where it - %excludes voxels (it also loops over patterns as well when it calls - %canlab_pattern_similarity - so not any slower to do this loop here) - - % mask.dat = expanded_mask; - % parcel_pattern_expression = apply_mask(dat, mask, varargin{:},'ignore_missing'); - - -end % Pattern expression - -end % Main function - - - -function [dat,parcels] = match_spaces(dat,parcels) -%code taken from apply_mask to make sure data are in same space - - -isdiff = compare_space(dat, parcels); - -if isdiff == 1 || isdiff == 2 % diff space, not just diff voxels - - % Both work, but resample_space does not require going back to original - % images on disk. - %mask = resample_to_image_space(mask, dat); - parcels = resample_space(parcels, dat, 'nearest'); - - if length(parcels.removed_voxels) == parcels.volInfo.nvox - disp('Warning: resample_space returned illegal length for removed voxels. Fixing...'); - parcels.removed_voxels = parcels.removed_voxels(parcels.volInfo.wh_inmask); - end - -end - -dat = remove_empty(dat); -nonemptydat = ~dat.removed_voxels; % remove these - -dat = replace_empty(dat); - -% Check/remove NaNs. This could be done in-object... -parcels.dat(isnan(parcels.dat)) = 0; - -% Replace if necessary -parcels = replace_empty(parcels); - - -% save which are in mask, but do not replace with logical, because mask may -% have weights we want to preserve -inparcelsdat = logical(parcels.dat); - - -% Remove out-of-mask voxels -% --------------------------------------------------- - -% mask.dat has full list of voxels -% need vox in both mask and original data mask - -if size(parcels.volInfo.image_indx, 1) == size(dat.volInfo.image_indx, 1) - n = size(parcels.volInfo.image_indx, 1); - - if size(nonemptydat, 1) ~= n % should be all vox OR non-empty vox - nonemptydat = zeroinsert(~dat.volInfo.image_indx, nonemptydat); - end - - if size(inparcelsdat, 1) ~= n - inparcelsdat = zeroinsert(~parcels.volInfo.image_indx, inparcelsdat); - end - - inboth = inparcelsdat & nonemptydat; - - % List in space of in-mask voxels in dat object. - % Remove these from the dat object - to_remove = ~inboth(dat.volInfo.wh_inmask); - - to_remove_parcels = ~inboth(parcels.volInfo.wh_inmask); - -elseif size(parcels.dat, 1) == size(dat.volInfo.image_indx, 1) - - % mask vox are same as total image vox - nonemptydat = zeroinsert(~dat.volInfo.image_indx, nonemptydat); - inboth = inparcelsdat & dat.volInfo.image_indx & nonemptydat; - - % List in space of in-mask voxels in dat object. - to_remove = ~inboth(dat.volInfo.wh_inmask); - - to_remove_parcels = ~inboth(parcels.volInfo.wh_inmask); - -elseif size(parcels.dat, 1) == size(dat.volInfo.wh_inmask, 1) - % mask vox are same as in-mask voxels in dat - inboth = inparcelsdat & dat.volInfo.image_indx(dat.volInfo.wh_inmask) & nonemptydat; - - % List in space of in-mask voxels in .dat field. - to_remove = ~inboth; - - to_remove_parcels = ~inboth; - -else - fprintf('Sizes do not match! Likely bug in resample_to_image_space.\n') - fprintf('Vox in mask: %3.0f\n', size(mask.dat, 1)) - fprintf('Vox in dat - image volume: %3.0f\n', size(dat.volInfo.image_indx, 1)); - fprintf('Vox in dat - image in-mask area: %3.0f\n', size(dat.volInfo.wh_inmask, 1)); - disp('Stopping to debug'); - keyboard -end - -dat = remove_empty(dat, to_remove); -parcels = remove_empty(parcels, to_remove_parcels); - -end diff --git a/CanlabCore/@image_vector/apply_mask.m b/CanlabCore/@image_vector/apply_mask.m index c4d7d84f..51bc1a92 100644 --- a/CanlabCore/@image_vector/apply_mask.m +++ b/CanlabCore/@image_vector/apply_mask.m @@ -1,6 +1,5 @@ function [dat, mask] = apply_mask(dat, mask, varargin) -% Apply a mask image (image filename or fmri_mask_image object) to an image_vector object -% stored in dat. +% apply_mask Apply a mask image (filename or fmri_mask_image object) to an image_vector object. % % This can be used to: % - Mask an image_vector or fmri_data object with a mask @@ -10,7 +9,26 @@ % The mask or weight map does not have to be in the same space as the dat; % it will be resampled to the space of the data in dat. % -% To extract pattern expression values for each ROI within a mask use extract_roi_averages() +% To extract pattern expression values for each ROI within a mask use +% extract_roi_averages(). +% +% :Usage: +% :: +% +% [dat, mask] = apply_mask(dat, mask, [optional inputs]) +% +% :Inputs: +% +% **dat:** +% An image_vector (or subclass, e.g., fmri_data, statistic_image) +% object containing one or more images. +% +% **mask:** +% A mask image filename, an fmri_mask_image object, or any +% image_vector-class object whose .dat field contains the mask +% weights. The mask is resampled to the data space if needed. +% If passed as a statistic_image, the .sig field is used to set +% non-significant voxels to zero before masking. % % :Optional Inputs: % @@ -43,6 +61,18 @@ % use with pattern expression only. scales expression by product of % l2 norms (norm(mask)*norm(dat)) % +% :Outputs: +% +% **dat:** +% Either (a) the input data object with out-of-mask voxels removed, +% or (b) when 'pattern_expression' / 'correlation' / +% 'cosine_similarity' is requested, a vector / structure of +% similarity values returned by canlab_pattern_similarity. +% +% **mask:** +% The mask after resampling to the data space and removal of empty +% voxels, returned for inspection. +% % :Examples: % :: % @@ -52,11 +82,11 @@ % [pattern_exp_values] = apply_mask(dat, weight map image, 'pattern_expression', 'ignore_missing') % [pattern_exp_values] = apply_mask(dat, weight map image, 'pattern_expression', 'ignore_missing','correlation') % -% % :See also: -% -% extract_roi_averages, to get individual region averages / local pattern expression -% apply_nps, which does whole-pattern and local regional expression +% - extract_roi_averages (individual region averages / local pattern expression) +% - apply_nps (whole-pattern and local regional expression) +% - canlab_pattern_similarity +% - resample_space % % .. % Notes: @@ -64,7 +94,6 @@ % 12/15/13: Luke Chang - added correlation option for pattern-expression % 5/10/2016: Phil Kragel - added cosine similarity % 8/2/2018: Tor Wager - if mask is a statistic_image object, apply .sig field -% % .. dopatternexpression = 0; % set options diff --git a/CanlabCore/@image_vector/check_image_filenames.m b/CanlabCore/@image_vector/check_image_filenames.m index 637ea588..c09660e9 100644 --- a/CanlabCore/@image_vector/check_image_filenames.m +++ b/CanlabCore/@image_vector/check_image_filenames.m @@ -1,34 +1,61 @@ function obj = check_image_filenames(obj, varargin) -% Check whether images listed in obj.fullpath actually exist +% check_image_filenames Check whether images listed in obj.fullpath actually exist on disk. % -% :Usage: -% :: +% Behavior: % -% obj = check_image_filenames(obj, ['noverbose']) -% -% :Behavior: % - If there are no file names, do nothing. % - If file names are entered and full path is not, attempt to find full % path. % - If full path info is entered, check to see if files exist. % Return output in obj.files_exist, and print a warning if only some exist. % -% Image names should be stored in .fullpath -% abbreviated image names may be stored in image_names. +% Image names should be stored in .fullpath; abbreviated image names may +% be stored in image_names. +% +% Note: fullpath should have full path to each volume in a string matrix, +% with trailing [,volume#] for 4-D images as per SPM style expanded list. +% image_names should have image name only for each volume. +% +% :Usage: +% :: +% +% obj = check_image_filenames(obj, ['noverbose']) +% +% :Inputs: +% +% **obj:** +% An image_vector / fmri_data object with .image_names and/or +% .fullpath fields. +% +% :Optional Inputs: +% +% **'noverbose':** +% Suppress informational messages. +% +% :Outputs: % -% :Note: -% -% fullpath should have full path to each volume in a string matrix, with -% trailing [,volume#] for 4-D images as per SPM style expanded list. +% **obj:** +% The input object with .fullpath populated (if found), +% .image_names normalized, and .files_exist populated as a +% logical column vector indicating whether each listed file +% exists. .history is updated. % -% image_names should have image name only for each volume +% :Examples: +% :: +% +% obj = check_image_filenames(obj); +% obj = check_image_filenames(obj, 'noverbose'); +% +% :See also: +% - read_from_file +% - expand_4d_filenames % % .. % May still be debugging issues with 3-D vs. 4-D files -% .. % -% Tor Wager: 7/2018 Added support for .gz file checking, avoiding spm_vol -% warning +% Tor Wager: 7/2018 Added support for .gz file checking, avoiding +% spm_vol warning +% .. verbose = isempty(strmatch('noverbose', varargin(cellfun(@ischar, varargin)))); % if 'noverbose' is entered, suppress output diff --git a/CanlabCore/@image_vector/compare_space.m b/CanlabCore/@image_vector/compare_space.m index 74045f6f..49967294 100644 --- a/CanlabCore/@image_vector/compare_space.m +++ b/CanlabCore/@image_vector/compare_space.m @@ -1,14 +1,42 @@ function isdiff = compare_space(obj, obj2) -% Compare spaces of two image_vector objects +% compare_space Compare image spaces of two image_vector objects. +% +% Compares the affine matrix, dimensions, and voxel count of two +% image_vector / fmri_data objects, plus their in-mask voxel +% configuration. % % :Usage: % :: % -% function isdiff = compare_space(obj, obj2) +% isdiff = compare_space(obj, obj2) +% +% :Inputs: +% +% **obj:** +% First image_vector / fmri_data object. +% +% **obj2:** +% Second image_vector / fmri_data object. +% +% :Outputs: +% +% **isdiff:** +% Integer code: +% +% - 0 if same +% - 1 if different spaces +% - 2 if no volInfo info for one or more objects +% - 3 if same space, but different in-mask voxels in .dat or +% volInfo.image_indx +% +% :Examples: +% :: +% +% d = compare_space(dat, mask); % -% Returns 0 if same, 1 if different spaces, 2 if no volInfo info for one or -% more objects. 3 if same space, but different in-mask voxels in .dat or -% volInfo.image_indx +% :See also: +% - resample_space +% - apply_mask if isempty(obj.volInfo) || isempty(obj2.volInfo) isdiff = 2; diff --git a/CanlabCore/@image_vector/enforce_variable_types.m b/CanlabCore/@image_vector/enforce_variable_types.m index a64678ff..6106943c 100644 --- a/CanlabCore/@image_vector/enforce_variable_types.m +++ b/CanlabCore/@image_vector/enforce_variable_types.m @@ -1,11 +1,55 @@ function obj = enforce_variable_types(obj) -% Re-casts variables in objects into standard data types, which can save space -% in memory. Also removes empty voxels as a space-saving device. +% enforce_variable_types Re-cast image_vector fields into standard data types to save space. % -% obj = enforce_variable_types(obj) +% Re-casts variables in objects into standard data types, which can save +% space in memory. Also removes empty voxels as a space-saving device. % % Needs testing to ensure that doing this does not break any other % display/computation functions. +% +% :Usage: +% :: +% +% obj = enforce_variable_types(obj) +% +% :Inputs: +% +% **obj:** +% An image_vector or subclass (fmri_data, statistic_image, atlas) +% object. +% +% :Outputs: +% +% **obj:** +% The input object with empty voxels removed and the following +% type conversions applied: +% +% - .dat -> single (or double for statistic_image with type 'T') +% - .volInfo.wh_inmask -> uint32 +% - .volInfo.xyzlist -> uint16 +% - .volInfo.cluster -> uint32 +% - For fmri_data, .mask is similarly down-cast. +% - For statistic_image, .sig -> logical and .p -> single +% (except for 'T' type, which retains higher precision .p). +% +% If the object lacks an 'image_metadata' property, a default +% image_metadata struct is initialized. +% +% :Examples: +% :: +% +% dat = enforce_variable_types(dat); +% +% :See also: +% - remove_empty +% - replace_empty +% +% .. +% Programmers' notes: +% Edited 02/26/2026 (Zizhuang Miao): force the .dat field of a +% statistic_image of type 'T' to be double, to enable more accurate +% estimates of p values. +% .. obj = remove_empty(obj); % EDITED: force the .dat field of a statistic_image to be double diff --git a/CanlabCore/@image_vector/expand_into_atlas_subregions.m b/CanlabCore/@image_vector/expand_into_atlas_subregions.m index 1a8a331f..d1600531 100644 --- a/CanlabCore/@image_vector/expand_into_atlas_subregions.m +++ b/CanlabCore/@image_vector/expand_into_atlas_subregions.m @@ -1,17 +1,58 @@ - -% fmri_data.subs - +% expand_into_atlas_subregions Expand a single-image data object into k images, one per atlas region. +% +% Take an image object (one image) and an integer-valued parcellation / +% atlas whose values indicate k regions, and expand the image object into +% a set of k images with the original values only for voxels in the kth +% region, and zero elsewhere. Useful for breaking a pattern mask into +% local atlas-defined subregions. +% % This is different from fmri_data.apply_parcellation, which returns % averages or weighted averages per image for a set of images in a data -% object. apply_parcellation can be used to return local pattern responses -% for any parcellation, but it does not return vectors with the local -% weights themselves. - -% expand into atlas subregions -% Take an image object (one image) and an integer-valued parcellation/atlas whose values -% indicate k regions, and expand the image object into a set of k images with -% the original values only for voxels in the kth region, and zero -% elsewhere. Useful for breaking a pattern mask into local atlas-defined subregions. +% object. apply_parcellation can be used to return local pattern +% responses for any parcellation, but it does not return vectors with +% the local weights themselves. +% +% NOTE: This file is a script-style template (no function declaration). +% It currently expects the variable 'nps' to exist in the workspace as +% the target image object, and uses BN_Atlas_274_noCb_uint16.nii as the +% parcellation. Adapt as needed before running. +% +% :Usage: +% :: +% +% % Set up nps (an fmri_data / image_vector object), then run: +% expand_into_atlas_subregions +% +% :Inputs (workspace variables): +% +% **nps:** +% Single-image image_vector / fmri_data object holding the +% pattern to be split by region. +% +% :Outputs (workspace variables): +% +% **target_obj:** +% The expanded object with one column per non-empty atlas region, +% each containing the original pattern values restricted to that +% region (zero elsewhere). target_obj.additional_info holds the +% retained region names. +% +% **r:** +% A region object built from the expanded data, with one element +% per contiguous region. region_names tracks the parent atlas +% label for each region in r. +% +% :See also: +% - fmri_data.apply_parcellation +% - select_voxels_by_value +% - region +% - canlab_pattern_similarity +% +% .. +% Programmers' notes: +% fmri_data.subs +% .. + % define target image obj to sample to : nps diff --git a/CanlabCore/@image_vector/flip.m b/CanlabCore/@image_vector/flip.m index 6d4d25c2..526fdb46 100644 --- a/CanlabCore/@image_vector/flip.m +++ b/CanlabCore/@image_vector/flip.m @@ -1,20 +1,69 @@ function dat = flip(dat, varargin) -% Flips images stored in an object left-to-right +% flip Flip images stored in an image_vector object left-to-right. +% +% Operates on a single-image (3-D) object only. The slice loop below +% iterates over Z planes of a 3-D volume; calling it on a multi-image +% (4-D after reconstruction) object would silently scramble data, so +% this method now errors instead. To flip a multi-image object, reduce +% it to a single image first (e.g. mean(obj), or get_wh_image(obj, k)). +% +% :Usage: +% :: +% +% dat = flip(dat, ['mirror']) +% +% :Inputs: +% +% **dat:** +% A single-image image_vector (or subclass) object. Errors if the +% object contains more than one image. % % :Optional Inputs: % -% Input 'mirror' to make a symmetrical image, averaging the left -% and right hemispheres +% **'mirror':** +% Make a symmetrical image, averaging the left and right +% hemispheres after flipping. +% +% :Outputs: +% +% **dat:** +% The input object with image data flipped left-to-right (and +% averaged with the original if 'mirror' was specified). The +% volInfo / mask is rebuilt around the new non-zero values. % % :Examples: % :: % -% dat = flip(dat, ['mirror']) +% dat = flip(dat) +% dat = flip(dat, 'mirror') +% dat = flip(mean(obj)) % flip the group-mean of a multi-image object +% +% :See also: +% - reconstruct_image +% - rebuild_volinfo_from_dat +% - replace_empty % % .. % Tor. may 2012 % .. +% Ensure padded state so reconstruct_image / rebuild_volinfo_from_dat see +% the full voxel space; otherwise the latter errors with +% "newdat MUST be size of ENTIRE image". +dat = replace_empty(dat); + +% Single-image guard: the slice loop below assumes a 3-D reconstruction. +% Without this, a multi-image object reconstructs to 4-D, the loop +% iterates only the first image's Z planes, and rebuild_volinfo_from_dat +% errors on a vector of the wrong length. +n_images = size(dat.dat, 2); +if n_images > 1 + error('image_vector:flip:multiImage', ... + ['flip() operates on a single-image (3-D) object, but this ' ... + 'object has %d images. Reduce to one image first, e.g. ' ... + 'mean(obj) or get_wh_image(obj, k).'], n_images); +end + vdat = reconstruct_image(dat); orig_dat = vdat(:); @@ -26,7 +75,7 @@ vdat = vdat(:); - % mirror + % mirror if ~isempty(varargin) && strcmp(varargin{1}, 'mirror') vdat = (orig_dat + vdat) / 2; end @@ -35,4 +84,3 @@ dat = rebuild_volinfo_from_dat(dat, vdat); end - diff --git a/CanlabCore/@image_vector/get_xyzmm_coordinates.m b/CanlabCore/@image_vector/get_xyzmm_coordinates.m index 4acddb08..763a1b69 100644 --- a/CanlabCore/@image_vector/get_xyzmm_coordinates.m +++ b/CanlabCore/@image_vector/get_xyzmm_coordinates.m @@ -1,5 +1,37 @@ function xyzmm = get_xyzmm_coordinates(obj) -% Extract non-empty, inmask coordinates for all voxels in an image object +% get_xyzmm_coordinates Return MNI/world (mm) coordinates for non-empty in-mask voxels. +% +% Extract non-empty, in-mask coordinates for all voxels in an image +% object and convert from voxel indices to mm using the affine matrix +% in obj.volInfo.mat. +% +% :Usage: +% :: +% +% xyzmm = get_xyzmm_coordinates(obj) +% +% :Inputs: +% +% **obj:** +% An image_vector (or subclass) object with a populated +% volInfo.xyzlist and volInfo.mat. Empty voxels are removed +% before coordinate conversion. +% +% :Outputs: +% +% **xyzmm:** +% An [n x 3] matrix of mm coordinates (one row per non-empty +% in-mask voxel). +% +% :Examples: +% :: +% +% xyz = get_xyzmm_coordinates(dat); +% +% :See also: +% - voxel2mm +% - remove_empty +% - reconstruct_image xyzvox = obj.volInfo.xyzlist; diff --git a/CanlabCore/@image_vector/history.m b/CanlabCore/@image_vector/history.m index 1458d879..c9df1077 100644 --- a/CanlabCore/@image_vector/history.m +++ b/CanlabCore/@image_vector/history.m @@ -1,5 +1,33 @@ function history(dat) -% Display history for image_vector object +% history Display history for image_vector object. +% +% Prints the .dat_descrip and .history fields of an image_vector +% (or subclass) object to the command window, separated by underline +% rows. +% +% :Usage: +% :: +% +% history(dat) +% +% :Inputs: +% +% **dat:** +% An image_vector (or subclass) object whose .history is a cell +% or character array of provenance strings. +% +% :Outputs: +% +% None. Output is printed to the command window. +% +% :Examples: +% :: +% +% history(dat); +% +% :See also: +% - descriptives +% - image_vector u = '_________________________________________________'; disp(u) diff --git a/CanlabCore/@image_vector/horzcat.m b/CanlabCore/@image_vector/horzcat.m deleted file mode 100644 index 6eefad63..00000000 --- a/CanlabCore/@image_vector/horzcat.m +++ /dev/null @@ -1,33 +0,0 @@ -function c = horzcat(varargin) -% Implements the horzcat ([a b]) operator on image_vector objects across voxels. -% Requires that each object has an equal number of columns and voxels -% -% :Usage: -% :: -% -% function s = horzcat(varargin) -% -% :Examples: -% :: -% -% c = [dat1 dat2]; -% -% .. -% Programmer Notes -% Created 3/14/14 by Luke Chang -% .. - -error('This method is deprecated. Use image_math instead.'); - -dat = []; -for i = 1:nargin - %Check if image_vector object - if ~isa(varargin{i}, 'image_vector') - error('Input Data is not an image_vector object') - end - - dat = [dat, varargin{i}.dat]; -end - -c = varargin{1}; -c.dat = dat; diff --git a/CanlabCore/@image_vector/ica.m b/CanlabCore/@image_vector/ica.m index b90d152a..0a6657ea 100644 --- a/CanlabCore/@image_vector/ica.m +++ b/CanlabCore/@image_vector/ica.m @@ -1,51 +1,90 @@ function icadat = ica(fmridat_obj, varargin) -% Spatial ICA of an fmri_data object -% - icadat = ica(fmridat_obj, [number of ICs to save]) -% - icadat is also an fmri_data object, with .dat field voxels x components +% ica Spatial ICA of an fmri_data object. +% +% Run spatial fastICA across the images stored in an fmri_data / +% image_vector object and return an fmri_data object whose columns are +% spatial component maps. The mixing (A) and separation (W) matrices are +% stored in icadat.additional_info{1} and {2}. +% +% :Usage: +% :: +% +% icadat = ica(fmridat_obj, [number of ICs to save]) +% +% :Inputs: +% +% **fmridat_obj:** +% An fmri_data (or image_vector) object. icadat is also an +% fmri_data object, with .dat field voxels x components. +% +% :Optional Inputs: +% +% **number of ICs:** +% Optional integer scalar specifying the number of independent +% components to retain. Default: 30. +% +% :Outputs: +% +% **icadat:** +% fmri_data object holding the spatial component maps in .dat +% (voxels x components) and the mixing / separation matrices in +% .additional_info{1} (A) and .additional_info{2} (W). % % :Notes: +% % - Spatial component maps are saved in columns of icadat.dat, or rows of icadat.dat' % - icasig = W * mixedsig % - icasig = icadat.dat' = W * fmridat_obj.dat' % -% W is the separation matrix, the inverse of the mixing matrix -% A is the mixing matrix, the inverse of the separation matrix -% -% A and W are stored in additional_info field of icadat in cells {1} and {2}, respectively -% A = ica_obj.additional_info{1}; -% W = ica_obj.additional_info{2}; -% -% Model: -% D = A * S, where: -% D = [n x v] dataset, images x voxels in fmridat_obj.dat' -% A = [n x k] mixing matrix (the inverse of the separation matrix if n = k) - -% W = [k x n] separation matrix, (the inverse of the mixing matrix if n = k) -% W' is a matrix whose columns are image series, e.g., time -% series. They reflect the expression of each component -% across images and can be used to analyze differences across -% image groups (conditions, time, etc.) -% S = [k x v] components matrix, with k components. S = icadat.dat' -% S = W * D; -% -% - Each image in D is a mixture of maps S defined by a row of A -% - Each column of A defines the expression of one map in S across images (e.g., time, or other image series) -% -% Reconstructed data: -% D_recon = A * S; -% figure; plot(D(:), D_recon(:), 'k.') -% A is related to D * S' (fmridat_obj.dat' * icadat.dat)'; -% -% Dual regression / back-reconstruction: -% B = pinv(S') * D'; % dual regression step 1, each data image n regressed on k spatial maps S; B = k x n -% B = pinv(icadat.dat) * fmridat_obj.dat; -% Note: fmridat_obj can be a different image object here, e.g., an independent dataset -% If fmridat_obj is the same dataset used to derive ICA components S, B is very similar to W -% -% S_hat = pinv(B') * fmridat_obj.dat'; % dual regression step 2 -% S_obj = fmridat_obj; -% S_obj.dat = S_hat'; -% montage(get_wh_image(S_obj, 1:4)); +% W is the separation matrix, the inverse of the mixing matrix. +% A is the mixing matrix, the inverse of the separation matrix. +% +% A and W are stored in additional_info field of icadat in cells {1} and {2}, respectively +% A = ica_obj.additional_info{1}; +% W = ica_obj.additional_info{2}; +% +% Model: +% D = A * S, where: +% +% D = [n x v] dataset, images x voxels in fmridat_obj.dat' +% A = [n x k] mixing matrix (the inverse of the separation matrix if n = k) +% W = [k x n] separation matrix, (the inverse of the mixing matrix if n = k) +% W' is a matrix whose columns are image series, e.g., time +% series. They reflect the expression of each component +% across images and can be used to analyze differences across +% image groups (conditions, time, etc.) +% S = [k x v] components matrix, with k components. S = icadat.dat' +% S = W * D; +% +% - Each image in D is a mixture of maps S defined by a row of A +% - Each column of A defines the expression of one map in S across images (e.g., time, or other image series) +% +% Reconstructed data: +% D_recon = A * S; +% figure; plot(D(:), D_recon(:), 'k.') +% A is related to D * S' (fmridat_obj.dat' * icadat.dat)'; +% +% Dual regression / back-reconstruction: +% B = pinv(S') * D'; % dual regression step 1, each data image n regressed on k spatial maps S; B = k x n +% B = pinv(icadat.dat) * fmridat_obj.dat; +% % Note: fmridat_obj can be a different image object here, e.g., an independent dataset +% % If fmridat_obj is the same dataset used to derive ICA components S, B is very similar to W +% +% S_hat = pinv(B') * fmridat_obj.dat'; % dual regression step 2 +% S_obj = fmridat_obj; +% S_obj.dat = S_hat'; +% montage(get_wh_image(S_obj, 1:4)); +% +% :Examples: +% :: +% +% icadat = ica(fmridat_obj, 20); +% orthviews(icadat); +% +% :See also: +% - icatb_fastICA +% - mahal +% - orthviews figure; plot(B(:), W(:), 'k.') diff --git a/CanlabCore/@image_vector/image_similarity_plot.m b/CanlabCore/@image_vector/image_similarity_plot.m index 281f86e2..83f3a653 100644 --- a/CanlabCore/@image_vector/image_similarity_plot.m +++ b/CanlabCore/@image_vector/image_similarity_plot.m @@ -1,55 +1,40 @@ function [stats, hh, hhfill, table_group, multcomp_group] = image_similarity_plot(obj, varargin) -% Associations between data images in object and set of 'spatial basis function' images (e.g., 'signatures' or pre-defined maps) -% - Similarity metric is point-biserial correlations or cosine-similarity -% -% Usage: +% image_similarity_plot Plot associations between data images and a set of spatial basis maps. +% +% Compares one or more input images to a specified set of a priori +% spatial basis maps (e.g., 'signatures' or pre-defined maps) and +% visualizes the similarity values as either a wedge or polar plot. +% +% - Similarity metric is point-biserial correlation (Pearson's r) by +% default; cosine similarity and binary overlap are also available. +% - If multiple images are entered, the 'average' option returns a plot +% with standard error bars and statistics on the significance of the +% correlation with each basis map (across input images) and the +% differences (inhomogeneity) in similarity across basis maps. +% - If a grouping variable is entered, statistics are calculated for +% multivariate differences across the groups of input images. Such +% differences are assessed treating the basis maps as variables and +% input images as cases. +% - There are many basis map sets ("mapset" in code) that can be +% specified by keyword. E.g., "NPSplus" includes the NPS map from +% Wager et al. 2013, Romantic Rejection classifier (Woo 2015), +% Negative emotion map (Chang 2015), and vicarious pain (Krishnan et +% al. 2016). Other sets are "bucknerlab" including 7 cortical [only] +% networks from the Buckner Lab's 1000-person resting-state analyses +% and "kragelemotion", including 7 emotion-predictive maps from +% Kragel 2015. +% - Be thoughtful about how your code is treating zero-valued voxels, +% which are typically missing data in brain images. The main +% calculations here are done by canlab_pattern_similarity.m, which +% treats zeros as missing in data images but not pattern basis maps +% (which can be binary or other 'signatures'). See +% "help canlab_pattern_similarity". +% +% :Usage: % :: % % [stats, hh, hhfill, table_group, multcomp_group] = image_similarity_plot(obj, 'average'); % -% - This is a method for an image_vector object that compares the spatial -% similarity of input image(s) to a specified set of a priori spatial basis maps. -% It returns similarity values (Pearson's r by default) to each a priori basis map, -% and a plot that shows these values. -% - If multiple images are entered, the 'average' function can return a plot with -% standard error bars and statistics on the significance of the correlation with each basis map -% (across input images) and the differences (inhomogeneity) in similarity across basis -% maps. -% - If a grouping variable is entered, statistics are calculated for -% multivariate differences across the groups of input images. Such -% differences are assessed treating the basis maps as variables and input -% images as cases. -% - There are many basis map sets ("mapset" in code) that can be specified by keyword. -% e.g., "NPSplus" includes the NPS map from Wager et al. 2013, Romantic Rejection -% classifier (Woo 2015), Negative emotion map (Chang 2015), and vicarious -% pain (Krishnan et al. 2016). Other sets are "bucknerlab" including 7 cortical [only] -% networks from the Buckner Lab's 1000-person resting-state analyses and -% "kragelemotion", including 7 emotion-predictive maps from Kragel 2015. -% - Be thoughtful about how your code is treating zero-valued voxels, which -% are typically missing data in brain images. The main calculations here -% are done by canlab_pattern_similarity.m, which treats zeros as missing in -% data images but not pattern basis maps (which can be binary or other -% 'signatures'. See "help canlab_pattern_similarity". -% -% .. -% Author and copyright information: -% -% Copyright (C) 2015 Tor Wager, stats added by Phil Kragel -% -% This program is free software: you can redistribute it and/or modify -% it under the terms of the GNU General Public License as published by -% the Free Software Foundation, either version 3 of the License, or -% (at your option) any later version. -% -% This program is distributed in the hope that it will be useful, -% but WITHOUT ANY WARRANTY; without even the implied warranty of -% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -% GNU General Public License for more details. -% -% You should have received a copy of the GNU General Public License -% along with this program. If not, see . -% .. -% % :Inputs: % % **obj:** @@ -221,46 +206,65 @@ % stats = image_similarity_plot(fmri_data(img), 'cosine_similarity', 'bucknerlab', 'colors', color); % % :See also: +% - tor_polar_plot +% - tor_wedge_plot +% - canlab_pattern_similarity +% - load_image_set % -% tor_polar_plot, tor_wedge_plot - % .. +% Author and copyright information: +% +% Copyright (C) 2015 Tor Wager, stats added by Phil Kragel +% +% This program is free software: you can redistribute it and/or modify +% it under the terms of the GNU General Public License as published by +% the Free Software Foundation, either version 3 of the License, or +% (at your option) any later version. +% +% This program is distributed in the hope that it will be useful, +% but WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +% GNU General Public License for more details. +% +% You should have received a copy of the GNU General Public License +% along with this program. If not, see . +% % Programmers' notes: % List dates and changes here, and author of changes -% List dates and changes here, and author of changes -% 11/30/2015 (Phil Kragel) -% - added anova (rm) comparing means across spatial bases -% - added anova (1-way) comparing means across groups for each spatial -% basis (e.g., for each buckner network) -% 12/15/2015 (Phil Kragel) -% - added option to omit plotting -% 5/10/2016 (Phil Kragel) -% - added option to use cosine similarity instead of Pearson -% 8/21/2017 (Stephan Geuter) -% - fixed header for printing similarity table -% 2017/09/07 Stephan Geuter -% - added option for percent overlap of binary masks (see also -% canlab_pattern_similarity.m and riverplot.m -% Changed metric selection to string format. -% 2018/1/9 tor: changed default colors for compat with wedge plot, -% debugged wedge plot with average option. -% 2018/1/16 Stephan: added pain PDM mediators as mapsets -% -% 2022/03/31 Ke Bo -% - added option for treating zero value in the map as real value rather -% than missing data -% 2023/03/02 Ke Bo -% - added option for compute error bar using standard deviation rather -% than standard error. This will suit for the dataset including bootstrap -% samples. -% 2023/10/23 Michael Sun -% - Groups are now plotted with different colors, and includes legend -% - Group Variable can now accept categorical -% - Plot labels and tables now include significance stars -% - 'notable' option now suppresses all tables. -% 2023/12/19 Lukas Van Oudenhove -% - debugged and improved wedge and polarplot code for multiple -% groups, see notes in code below for details +% 11/30/2015 (Phil Kragel) +% - added anova (rm) comparing means across spatial bases +% - added anova (1-way) comparing means across groups for each spatial +% basis (e.g., for each buckner network) +% 12/15/2015 (Phil Kragel) +% - added option to omit plotting +% 5/10/2016 (Phil Kragel) +% - added option to use cosine similarity instead of Pearson +% 8/21/2017 (Stephan Geuter) +% - fixed header for printing similarity table +% 2017/09/07 Stephan Geuter +% - added option for percent overlap of binary masks (see also +% canlab_pattern_similarity.m and riverplot.m +% Changed metric selection to string format. +% 2018/1/9 tor: changed default colors for compat with wedge plot, +% debugged wedge plot with average option. +% 2018/1/16 Stephan: added pain PDM mediators as mapsets +% +% 2022/03/31 Ke Bo +% - added option for treating zero value in the map as real value rather +% than missing data +% 2023/03/02 Ke Bo +% - added option for compute error bar using standard deviation rather +% than standard error. This will suit for the dataset including bootstrap +% samples. +% 2023/10/23 Michael Sun +% - Groups are now plotted with different colors, and includes legend +% - Group Variable can now accept categorical +% - Plot labels and tables now include significance stars +% - 'notable' option now suppresses all tables. +% 2023/12/19 Lukas Van Oudenhove +% - debugged and improved wedge and polarplot code for multiple +% groups, see notes in code below for details +% .. % PRELIMINARIES % ------------------------------------------------------------------------ diff --git a/CanlabCore/@image_vector/image_vector.m b/CanlabCore/@image_vector/image_vector.m index b26c637b..35c1874f 100644 --- a/CanlabCore/@image_vector/image_vector.m +++ b/CanlabCore/@image_vector/image_vector.m @@ -1,14 +1,18 @@ classdef image_vector -% image_vector: An object that allows for storage and manipulation of neuroimaging data +% image_vector An object class for storage and manipulation of neuroimaging data. +% +% image_vector stores brain image data in a flat (2-D) voxels x images +% matrix together with the meta-data needed to round-trip back to 3-D +% image space. It is the superclass of fmri_data, statistic_image, and +% atlas; you will rarely create image_vector objects directly. Use one +% of those subclasses instead. % -% ------------------------------------------------------------------------- % Features and philosophy: -% ------------------------------------------------------------------------- % -% - Store image data in a flat (2-d), space-efficient voxels x images matrix -% - Save space: Tools to remove out-of-image voxels and empty (zero/NaN) voxels, store data in single-precision format +% - Store image data in a flat (2-d), space-efficient voxels x images matrix +% - Save space: Tools to remove out-of-image voxels and empty (zero/NaN) voxels, store data in single-precision format % - Analysis-friendly: 2-d matrices can be read and analyzed in multiple packages/algorithms -% - Meta-data included to convert back to 3-d image volume space, +% - Meta-data included to convert back to 3-d image volume space, % with easy tools (methods) to reconstruct and visualize images % Built-in resampling makes it easy to compare/combine datasets with different voxel sizes and image bounding boxes % Reduces overhead for statisticians/data scientists unfamiliar with neuroimaging to apply their algorithms. @@ -20,188 +24,210 @@ % - Analysis (ica, mahal, image_math, and many more in the fmri_data subclass) % - Provenance: Ability to track and update history of changes to objects % -% image_vector is a superclass of several other object types: +% image_vector is a superclass of several other object types: % fmri_data % statistic_image % atlas % -% These object types have additional analysis methods and properties, and -% are most often used. You will rarely create an image_vector object -% directly. Typically you would create instances of one of the classes above. -% -% ------------------------------------------------------------------------- -% To construct/create a new instance of an object: -% ------------------------------------------------------------------------- -% -% As with any neuroimaging-data object class in this toolbox -% (image_vector, fmri_data, statistic_image, atlas), you can create an object by -% specifying the names of a set of images you want to load in and passing them into the -% function with the same name as the object class (e.g., fmri_data). This -% will create an object with image data and image space information attached. -% -% RECOMMENDED: Use fmri_data instead of image_vector to load image data: -% data_obj = fmri_data(image_names); -% -% ...where image_names is a character array or 1 x n cell array of strings -% -% EXAMPLE: Create an object from a Nifti .nii file (or analyze .img file) -% fname = which('brainmask_canlab.nii') -% obj = image_vector(fname); -% -% You can also manually construct an object by entering its constituent -% fields in key, value pairs. -% For the object to be valid, these much match one another in size/space/etc, and you -% may get errors later if they do not match. -% -% EXAMPLE: Here we create a empty image_vector object assign the image in fname -% to the .fullpath property. the image_vector constructor will read in the -% image stored in fname -% -% obj = image_vector('fullpath', fname, 'history', {'loaded from file.'}); -% -% EXAMPLE: The code below first loads a sample atlas object and then creates an image_vector object -% from its fields. Then, we re-cast it as an fmri_data object, which is -% generally more useful and has more associated methods. -% -% obj = load_atlas('cerebellum'); -% obj_with_region_indices = image_vector('dat', single(obj.dat), 'volInfo', obj.volInfo, 'removed_voxels', obj.removed_voxels, 'removed_images', obj.removed_images, 'image_names', obj.image_names, 'noverbose'); -% obj_with_region_indices = fmri_data(obj_with_region_indices); -% orthviews(obj_with_region_indices) -% -% You can also enter a structure, and image_vector will parse input fields from the structure -% % Here we use a structure to define a custom .dat field that overwrites the -% % one read from the file in fname -% s = struct('dat', [1:576094]', 'source_notes', {'example object'}, 'fullpath', fname); -% obj = image_vector(s); -% -% ------------------------------------------------------------------------- -% Properties and methods -% ----------------------------------------------------------------------- -% Properties are data fields associated with an object. -% Type the name of an object (class instance) you create to see its -% properties, and a link to its methods (things you can run specifically -% with this object type). For example: After creating an fmri_data object -% called fmri_dat, as above, type fmri_dat to see its properties. -% -% There are many other methods that you can apply to fmri_data objects to -% do different things. -% - Try typing methods(fmri_data) for a list. -% - You always pass in an fmri_data object as the first argument. -% - Methods include utilities for many functions - e.g.,: -% - resample_space(fmri_dat) resamples the voxels -% - write(fmri_dat) writes an image file to disk (careful not to overwrite by accident!) -% - regress(fmri_dat) runs multiple regression -% - predict(fmri_dat) runs cross-validated machine learning/prediction algorithms -% -% Key properties and methods (a partial list; type doc image_vector for more): -% ------------------------------------------------------------------------- -% image_vector Properties: -% dat - Image data, a [voxels x images] matrix, single-format -% fullpath - List of image names loaded into object with full paths -% history - History of object processing, for provenance -% image_names - List of image names loaded into object, no paths -% removed_images - Vector of images that have been removed (saves space; see remove_empty.m, replace_empty.m) -% removed_voxels - Vector of empty in-mask voxels that have been removed (saves space; see remove_empty.m, replace_empty.m) -% volInfo - Structure with info on brain mask (saves space) and mapping voxels to brain space -% -% image_vector Methods: -% General: -% . descriptives - Get descriptives for an fmri_data or other image_vector object -% enforce_variable_types - Re-casts variables in objects into standard data types, which can save +% These object types have additional analysis methods and properties, +% and are most often used. You will rarely create an image_vector object +% directly. Typically you would create instances of one of the classes +% above. +% +% :Usage: +% :: +% +% obj = image_vector(filename_or_fields) +% obj = image_vector('fullpath', fname, 'history', {'loaded from file.'}) +% +% :Inputs: +% +% **filename_or_fields:** +% Either a Nifti / Analyze filename to read, a structure whose +% fields will be parsed into the object, or a sequence of +% key/value pairs assigning fields directly. See the +% constructor body and Examples below for usage. +% +% :Construction: +% +% As with any neuroimaging-data object class in this toolbox +% (image_vector, fmri_data, statistic_image, atlas), you can create +% an object by specifying the names of a set of images you want to +% load in and passing them into the function with the same name as +% the object class (e.g., fmri_data). This will create an object +% with image data and image space information attached. +% +% RECOMMENDED: Use fmri_data instead of image_vector to load image data: +% data_obj = fmri_data(image_names); +% +% ...where image_names is a character array or 1 x n cell array of strings. +% +% EXAMPLE: Create an object from a Nifti .nii file (or analyze .img file) +% fname = which('brainmask_canlab.nii'); +% obj = image_vector(fname); +% +% You can also manually construct an object by entering its +% constituent fields in key, value pairs. For the object to be valid, +% these must match one another in size/space/etc, and you may get +% errors later if they do not match. +% +% EXAMPLE: Create an empty image_vector object and assign the image +% in fname to the .fullpath property. The image_vector constructor +% will read in the image stored in fname. +% +% obj = image_vector('fullpath', fname, 'history', {'loaded from file.'}); +% +% EXAMPLE: The code below first loads a sample atlas object and then +% creates an image_vector object from its fields. Then, we re-cast it +% as an fmri_data object, which is generally more useful and has more +% associated methods. +% +% obj = load_atlas('cerebellum'); +% obj_with_region_indices = image_vector('dat', single(obj.dat), 'volInfo', obj.volInfo, 'removed_voxels', obj.removed_voxels, 'removed_images', obj.removed_images, 'image_names', obj.image_names, 'noverbose'); +% obj_with_region_indices = fmri_data(obj_with_region_indices); +% orthviews(obj_with_region_indices) +% +% You can also enter a structure, and image_vector will parse input +% fields from the structure. Here we use a structure to define a +% custom .dat field that overwrites the one read from the file in fname: +% +% s = struct('dat', [1:576094]', 'source_notes', {'example object'}, 'fullpath', fname); +% obj = image_vector(s); +% +% :Properties and methods: +% +% Properties are data fields associated with an object. Type the name +% of an object (class instance) you create to see its properties, and +% a link to its methods (things you can run specifically with this +% object type). For example: After creating an fmri_data object +% called fmri_dat, as above, type fmri_dat to see its properties. +% +% There are many other methods that you can apply to fmri_data +% objects to do different things. +% +% - Try typing methods(fmri_data) for a list. +% - You always pass in an fmri_data object as the first argument. +% - Methods include utilities for many functions - e.g.: +% +% - resample_space(fmri_dat) resamples the voxels +% - write(fmri_dat) writes an image file to disk (careful not to +% overwrite by accident!) +% - regress(fmri_dat) runs multiple regression +% - predict(fmri_dat) runs cross-validated machine +% learning/prediction algorithms +% +% Key properties and methods (a partial list; type doc image_vector for more): +% +% image_vector Properties: +% +% dat - Image data, a [voxels x images] matrix, single-format +% fullpath - List of image names loaded into object with full paths +% history - History of object processing, for provenance +% image_names - List of image names loaded into object, no paths +% removed_images - Vector of images that have been removed (saves space; see remove_empty.m, replace_empty.m) +% removed_voxels - Vector of empty in-mask voxels that have been removed (saves space; see remove_empty.m, replace_empty.m) +% volInfo - Structure with info on brain mask (saves space) and mapping voxels to brain space +% +% image_vector Methods: +% +% General: +% +% descriptives - Get descriptives for an fmri_data or other image_vector object +% enforce_variable_types - Re-casts variables in objects into standard data types, which can save % flip - Flips images stored in an object left-to-right -% history - Display history for image_vector object -% write - Write an image_vector object to hard drive as an Analyze image (uses .fullpath field for image names) -% Data extraction: -% apply_atlas - Computes the mean value or pattern expression for each reference region specified in an atlas object -% apply_mask - Apply a mask image (image filename or fmri_mask_image object) to an image_vector object -% apply_parcellation - Computes the mean value or pattern expression for each parcel specified in a data object -% extract_gray_white_csf - Extracts mean values (values) and top 5 component scores (components) -% extract_roi_averages - This image_vector method a extracts and averages data stored in an fmri_data object -% -% Handling brain space and image selection: -% compare_space - Compare spaces of two image_vector objects -% get_wh_image - For an image_vector with multiple images (cases, contrasts, etc.), select a subset. -% reconstruct_image - Reconstruct a 3-D or 4-D image from image_vector object obj -% remove_empty - remove vox: logical vector of custom voxels to remove, VOX x 1 -% reparse_contiguous - Re-construct list of contiguous voxels in an image based on in-image -% replace_empty - Replace empty/missing values in an image data object -% resample_space - Resample the images in an fmri_data object (obj) to the space of another -% -% Display and visualization: -% display_slices - Creates 3 separate montage views - ax, cor, sagg in a special figure window -% histogram - Create a histogram of image values or a series of histograms for each +% history - Display history for image_vector object +% write - Write an image_vector object to hard drive as an Analyze image (uses .fullpath field for image names) +% +% Data extraction: +% +% apply_atlas - Computes the mean value or pattern expression for each reference region specified in an atlas object +% apply_mask - Apply a mask image (image filename or fmri_mask_image object) to an image_vector object +% apply_parcellation - Computes the mean value or pattern expression for each parcel specified in a data object +% extract_gray_white_csf - Extracts mean values (values) and top 5 component scores (components) +% extract_roi_averages - This image_vector method a extracts and averages data stored in an fmri_data object +% +% Handling brain space and image selection: +% +% compare_space - Compare spaces of two image_vector objects +% get_wh_image - For an image_vector with multiple images (cases, contrasts, etc.), select a subset. +% reconstruct_image - Reconstruct a 3-D or 4-D image from image_vector object obj +% remove_empty - remove vox: logical vector of custom voxels to remove, VOX x 1 +% reparse_contiguous - Re-construct list of contiguous voxels in an image based on in-image +% replace_empty - Replace empty/missing values in an image data object +% resample_space - Resample the images in an fmri_data object (obj) to the space of another +% +% Display and visualization: +% +% display_slices - Creates 3 separate montage views - ax, cor, sagg in a special figure window +% histogram - Create a histogram of image values or a series of histograms for each % image_similarity_plot - Associations between images in object and set of 'spatial basis function' images (e.g., 'signatures' or pre-defined maps) -% isosurface - Create and visualize an isosurface created from the boundaries in an image object. -% montage - Create a montage of an image_vector (or statistic_image or fmri_data) -% orthviews - display SPM orthviews for CANlab image_vector (or fmri_data, statistic_image) object -% pattern_surf_plot_mip - axial maximum intensity projection pattern surface plot -% sagg_slice_movie - Movie of successive differences (sagittal slice) +% isosurface - Create and visualize an isosurface created from the boundaries in an image object. +% montage - Create a montage of an image_vector (or statistic_image or fmri_data) +% orthviews - display SPM orthviews for CANlab image_vector (or fmri_data, statistic_image) object +% pattern_surf_plot_mip - axial maximum intensity projection pattern surface plot +% sagg_slice_movie - Movie of successive differences (sagittal slice) % slices - Create a montage of single-slice results for every image in an image_vector object % surface - Render image data on brain surfaces; options for cutaways and canonical surfaces -% wedge_plot_by_atlas - Plot a data object or 'signature' pattern divided into local regions +% wedge_plot_by_atlas - Plot a data object or 'signature' pattern divided into local regions % -% Data processing and analysis: -% ica - Spatial ICA of an fmri_data object +% Data processing and analysis: +% +% ica - Spatial ICA of an fmri_data object % image_math - Perform simple mathematical and boolean operations on image objects (see also plus, minus, power) % mahal - Mahalanobis distance for each image in a set compared to others in the set -% mean - Mean across a set of images. Returns a new image_vector object. -% preprocess - Preprocesses data in an image_vector (e.g., fmri_data) object; many options for filtering and outlier id -% qc_metrics_second_level - Quality metrics for a 2nd-level analysis (set of images from different subjects) -% searchlight - Run searchlight multivariate prediction/classification on an image_vector +% mean - Mean across a set of images. Returns a new image_vector object. +% preprocess - Preprocesses data in an image_vector (e.g., fmri_data) object; many options for filtering and outlier id +% qc_metrics_second_level - Quality metrics for a 2nd-level analysis (set of images from different subjects) +% searchlight - Run searchlight multivariate prediction/classification on an image_vector % threshold - Threshold image_vector (or fmri_data or fmri_obj_image) object based on raw threshold values -% union - ...and intersection masks for two image_vector objects -% -% ------------------------------------------------------------------------- -% Examples and help: -% ------------------------------------------------------------------------- -% -% To list properties and methods for this object, type: -% doc image_vector, methods(image_vector) -% -% Example 1: -% Load a sample dataset into an fmri_data object (subclass of image_vector) -% This loads one of a set of named image collections used in demos/help: -% data_obj = load_image_set('emotionreg'); -% -% You can load the same images manually, by locating the files, listing -% their names in a character array (or 1 x n cell array of strings), and -% then passing those into fmri_data: -% -% filedir = what(fullfile('CanlabCore', 'Sample_datasets', 'Wager_et_al_2008_Neuron_EmotionReg')); -% image_names = filenames(fullfile(filedir.path, '*img')); -% data_obj = fmri_data(image_names); -% -% Now you can interact with the object. Try, e.g.,: -% methods(data_obj) % List methods for object type -% plot(data_obj) % Custom fmri_data specific plots -% t = ttest(data_obj); % Perform a voxel-wise one-sample t-test across images -% t = threshold(t, .005, 'unc', 'k', 10); % Re-threshold with extent threshold of 10 contiguous voxels -% r = region(t); % Turn t-map into a region object with one element per contig region +% union - ...and intersection masks for two image_vector objects +% +% :Examples: +% :: +% +% % To list properties and methods for this object, type: +% doc image_vector, methods(image_vector) +% +% % Example 1: +% % Load a sample dataset into an fmri_data object (subclass of image_vector) +% % This loads one of a set of named image collections used in demos/help: +% data_obj = load_image_set('emotionreg'); +% +% % You can load the same images manually, by locating the files, listing +% % their names in a character array (or 1 x n cell array of strings), and +% % then passing those into fmri_data: +% filedir = what(fullfile('CanlabCore', 'Sample_datasets', 'Wager_et_al_2008_Neuron_EmotionReg')); +% image_names = filenames(fullfile(filedir.path, '*img')); +% data_obj = fmri_data(image_names); +% +% % Now you can interact with the object. Try, e.g.,: +% methods(data_obj) % List methods for object type +% plot(data_obj) % Custom fmri_data specific plots +% t = ttest(data_obj); % Perform a voxel-wise one-sample t-test across images +% t = threshold(t, .005, 'unc', 'k', 10); % Re-threshold with extent threshold of 10 contiguous voxels +% r = region(t); % Turn t-map into a region object with one element per contig region % % For more examples and walkthroughs, see the CANlab_help_examples % repository at https://github.com/canlab/CANlab_help_examples % % Some example tutorials: -% canlab_help_1_installing_tools -% canlab_help_2_load_a_sample_dataset -% canlab_help_3_voxelwise_t_test_walkthrough -% canlab_help_4_write_data_to_image_file_format -% canlab_help_5_regression_walkthrough -% -% License and authors: -% ------------------------------------------------------------------------- -% By Tor Wager and CANlab members and collaborators -% GNU General Public License; see http://www.gnu.org/licenses/ -% See Programmers' Notes in code for more info -% -% -% ------------------------------------------------------------------------- -% See also: -% fmri_data -% statistic_image -% atlas -% region - +% canlab_help_1_installing_tools +% canlab_help_2_load_a_sample_dataset +% canlab_help_3_voxelwise_t_test_walkthrough +% canlab_help_4_write_data_to_image_file_format +% canlab_help_5_regression_walkthrough +% +% License and authors: +% By Tor Wager and CANlab members and collaborators. +% GNU General Public License; see http://www.gnu.org/licenses/. +% See Programmers' Notes in code for more info. +% +% :See also: +% - fmri_data +% - statistic_image +% - atlas +% - region +% % .. % Author and copyright information: % diff --git a/CanlabCore/@image_vector/interpolate.m b/CanlabCore/@image_vector/interpolate.m index e522f114..bfbd922b 100644 --- a/CanlabCore/@image_vector/interpolate.m +++ b/CanlabCore/@image_vector/interpolate.m @@ -1,30 +1,45 @@ function dat = interpolate(dat, varargin) -% Interpolate over missing values in image_vector object +% interpolate Interpolate over missing values in an image_vector object. +% +% Use when there are some missing values in the mask image. Performs +% 3-D linear interpolation to fill in all values in the original mask. +% +% E.g., for a standard brain image space that is 91 x 109 x 91, you may +% have 300,000 in-mask values. Only 150,000 of these may be defined in +% the image, however, and the rest are missing (0 or NaN). This function +% will return a dat image with non-missing values for all 300,000 voxels +% (the "in-mask" space). It will not return values for all voxels in +% the 91 x 109 x 91 space, however. +% +% Note: This function does not upsample the data now, but could be +% extended to do so fairly easily. % % :Usage: % :: % -% dat = interpolate(dat, varargin) +% dat = interpolate(dat, [optional inputs]) +% +% :Inputs: % -% :Input: -% image_vector object (dat; e.g., an fmri_data object) +% **dat:** +% image_vector object (e.g., an fmri_data object). % -% Use when there are some missing values in the mask image -% Performs 3-D linear interpolation to fill in all values in the original -% mask. +% :Outputs: +% +% **dat:** +% The input object with .dat populated for all in-mask voxels by +% 3-D linear interpolation across the existing values, and +% .removed_voxels reset to false. +% +% :Examples: +% :: % -% e.g., For a standard brain image space that is 91 x 109 x 91, you may -% have 300,000 in-mask values. Only 150,000 of these may be defined in the -% image, however, and the rest are missing (0 or NaN). -% This function will return a dat image with non-missing values for all -% 300,000 voxels (the "in-mask" space). -% It will not return values for all voxels in the 91 x 109 x 91 space, -% however. +% dat = interpolate(dat); % -% :Note: -% This function does not upsample the data now, but could be extended -% to do so fairly easily. -% +% :See also: +% - griddata +% - define_sampling_space +% - reconstruct_image upsamplevalue = 1; % values > 1 would upsample the data diff --git a/CanlabCore/@image_vector/isosurface.m b/CanlabCore/@image_vector/isosurface.m index d7114389..ad592924 100644 --- a/CanlabCore/@image_vector/isosurface.m +++ b/CanlabCore/@image_vector/isosurface.m @@ -1,75 +1,122 @@ function [p, mesh_struct, my_isosurface, my_isocap] = isosurface(obj, varargin) -% Create and visualize an isosurface created from the boundaries in an image object. -% -% [p, mesh_struct, my_isosurface, my_isocap] = isosurface(obj, varargin) -% -% - p is a patch handle to graphics patch object showing surface and isocaps -% - mesh_struct has meshgrid with x, y, z coordinates in mm, and volume data -% - my_isosurface and my_isocap are structures with .faces and .vertices, -% which you can visualize using patch() -% -% Options: -% case 'thresh', mythresh = varargin{i + 1}; varargin{i + 1} = []; -% -% case 'nosmooth', dosmooth = false; -% case 'smoothbox', mysmoothbox = varargin{i + 1}; varargin{i + 1} = []; -% case 'sd', mygaussstd = varargin{i + 1}; varargin{i + 1} = []; -% -% case 'color', mycolor = varargin{i + 1}; varargin{i + 1} = []; -% -% -% case 'xlim', xlim = varargin{i + 1}; varargin{i + 1} = []; -% case 'ylim', ylim = varargin{i + 1}; varargin{i + 1} = []; -% case 'zlim', zlim = varargin{i + 1}; varargin{i + 1} = []; -% -% Examples: -% % ------------------------------------------------------ -% -% % An example cortical brain surface -% ------------------------------------------------------------------------ -% -% anat = fmri_data(which('keuken_2014_enhanced_for_underlay.img'), 'noverbose'); -% figure; -% p = isosurface(anat, 'thresh', 140, 'nosmooth'); -% set(p, 'FaceAlpha', 1); -% view(132, 6); -% lightRestoreSingle; -% -% Make a brain-bottom cutaway: -% ------------------------------------------------------------------------ -% -% create_figure('cutaway'); -% p = isosurface(anat, 'thresh', 140, 'nosmooth', 'zlim', [-Inf 20]); -% view(132, 30); -% -% A coronal cutaway around the nucleus accumbens: -% ------------------------------------------------------------------------ -% -% create_figure('cutaway'); -% p = isosurface(anat, 'thresh', 140, 'nosmooth', 'ylim', [-Inf 10]); -% view(132, 30); -% -% An often-used CANlab 3-d cutaway -% ------------------------------------------------------------------------ -% create_figure('cutaway'); -% anat = fmri_data(which('keuken_2014_enhanced_for_underlay.img'), 'noverbose'); -% p = isosurface(anat, 'thresh', 140, 'nosmooth', 'ylim', [-Inf -30]); -% p2 = isosurface(anat, 'thresh', 140, 'nosmooth', 'xlim', [-Inf 0], 'YLim', [-30 Inf]); -% alpha 1 ; lightRestoreSingle; view(135, 30); colormap gray; -% p3 = addbrain('limbic hires'); -% set(p3, 'FaceAlpha', .6, 'FaceColor', [.5 .5 .5]); -% delete(p3(3)); p3(3) = []; -% lightRestoreSingle; -% surface_handles = [p p2 p3]; -% -% Another example using a different group average image: -% ------------------------------------------------------------------------ -% dat = fmri_data(which('icbm152_2009_symm_enhanced_for_cutaways.nii'), 'noverbose'); -% figure; -% [p, mesh_struct] = isosurface(dat, 'percentagethresh', 80); -% alpha 1 ; camlight, lightRestoreSingle; view(135, 30); colormap gray -% -% See also: tor_3d, cluster_cutaway, cluster_image_shape +% isosurface Create and visualize an isosurface from the boundaries in an image object. +% +% Builds a 3-D isosurface (and isocaps) from an image_vector / fmri_data +% object. Useful for rendering brain anatomy, cutaways, and surface +% boundaries. +% +% :Usage: +% :: +% +% [p, mesh_struct, my_isosurface, my_isocap] = isosurface(obj, [optional inputs]) +% +% :Inputs: +% +% **obj:** +% An image_vector / fmri_data object. The function reconstructs a +% 3-D volume from obj and runs Matlab's isosurface / isocaps on it. +% +% :Optional Inputs: +% +% **'thresh':** +% Isosurface threshold value. Default: 0. +% +% **'percentagethresh':** +% If specified, sets the threshold to the given percentile of the +% (smoothed) volume. +% +% **'nosmooth':** +% Skip the 3-D Gaussian smoothing step (default: smoothing on). +% +% **'smoothbox':** +% Smoothing kernel size (passed to smooth3). Default: 3. +% +% **'sd':** +% Gaussian SD for smoothing (passed to smooth3). Default: 1. +% +% **'color':** +% RGB triplet for the patch face color. Default: [.5 .5 .5]. +% +% **'xlim' / 'ylim' / 'zlim':** +% Two-element [min max] vectors (mm) used to clip the volume +% before extracting the surface (for cutaways). +% +% **'zshift':** +% Scalar shift (in axis units) applied to the Z coordinates of the +% surface and isocap vertices. Default: 0. +% +% **'noverbose':** +% Suppress verbose output. +% +% :Outputs: +% +% **p:** +% Patch handle(s) to the graphics patch object showing surface and +% isocaps. +% +% **mesh_struct:** +% Struct with meshgrid X, Y, Z coordinates (mm) and the volume +% data used to build the surface. +% +% **my_isosurface:** +% Struct with .faces and .vertices fields for the isosurface. +% +% **my_isocap:** +% Struct with .faces and .vertices fields for the isocap. +% +% :Examples: +% :: +% +% % An example cortical brain surface +% % ------------------------------------------------------------------------ +% +% anat = fmri_data(which('keuken_2014_enhanced_for_underlay.img'), 'noverbose'); +% figure; +% p = isosurface(anat, 'thresh', 140, 'nosmooth'); +% set(p, 'FaceAlpha', 1); +% view(132, 6); +% lightRestoreSingle; +% +% % Make a brain-bottom cutaway: +% % ------------------------------------------------------------------------ +% +% create_figure('cutaway'); +% p = isosurface(anat, 'thresh', 140, 'nosmooth', 'zlim', [-Inf 20]); +% view(132, 30); +% +% % A coronal cutaway around the nucleus accumbens: +% % ------------------------------------------------------------------------ +% +% create_figure('cutaway'); +% p = isosurface(anat, 'thresh', 140, 'nosmooth', 'ylim', [-Inf 10]); +% view(132, 30); +% +% % An often-used CANlab 3-d cutaway +% % ------------------------------------------------------------------------ +% create_figure('cutaway'); +% anat = fmri_data(which('keuken_2014_enhanced_for_underlay.img'), 'noverbose'); +% p = isosurface(anat, 'thresh', 140, 'nosmooth', 'ylim', [-Inf -30]); +% p2 = isosurface(anat, 'thresh', 140, 'nosmooth', 'xlim', [-Inf 0], 'YLim', [-30 Inf]); +% alpha 1 ; lightRestoreSingle; view(135, 30); colormap gray; +% p3 = addbrain('limbic hires'); +% set(p3, 'FaceAlpha', .6, 'FaceColor', [.5 .5 .5]); +% delete(p3(3)); p3(3) = []; +% lightRestoreSingle; +% surface_handles = [p p2 p3]; +% +% % Another example using a different group average image: +% % ------------------------------------------------------------------------ +% dat = fmri_data(which('icbm152_2009_symm_enhanced_for_cutaways.nii'), 'noverbose'); +% figure; +% [p, mesh_struct] = isosurface(dat, 'percentagethresh', 80); +% alpha 1 ; camlight, lightRestoreSingle; view(135, 30); colormap gray +% +% :See also: +% - tor_3d +% - cluster_cutaway +% - cluster_image_shape +% - reconstruct_image +% - smooth3 % ------------------------------------------------------ % defaults diff --git a/CanlabCore/@image_vector/mahal.m b/CanlabCore/@image_vector/mahal.m index 2eb42e5f..95dbdedc 100644 --- a/CanlabCore/@image_vector/mahal.m +++ b/CanlabCore/@image_vector/mahal.m @@ -1,8 +1,12 @@ function [D2, D2_expected, pval, wh_outlier_uncorr, wh_outlier_corr] = mahal(obj, varargin) -% Mahalanobis distance for each image in a set compared to others in the set +% mahal Mahalanobis distance for each image in a set compared to others in the set. +% % Lower p-values reflect higher outlier status for an image. % -% [D2, D2_expected, pval, wh_outlier_uncorr, wh_outlier_corr] = mahal(obj, varargin) +% :Usage: +% :: +% +% [D2, D2_expected, pval, wh_outlier_uncorr, wh_outlier_corr] = mahal(obj, [optional inputs]) % % Mahalanobis distance, which is a measure of how different each image is from the rest of the images. % It's based on the squared distance across images, just like the typical least-squares solution we @@ -28,39 +32,70 @@ % and the relationship between the observed and expected distance. A positive deviation from the line means % the image is more unusual than expected. % -% Optional inputs: -% 'corr' : use correlation matrix of images instead of covariance. -% Insensitive to differences in scale and mean of data, and thus more -% sensitive to changes in pattern across image. +% :Inputs: % -% Outputs: +% **obj:** +% An image_vector / fmri_data object. Each column of obj.dat is +% treated as one observation (image) in the multivariate space. % -% wh_outlier_uncorr -% Logical vectors of which images have mahalanobis distances with p < .05 -% and values greater than the median +% :Optional Inputs: % -% wh_outlier_corr -% Logical vectors of which images have mahalanobis distances with p < .05 -% corrected (Bonferroni), and values greater than the median +% **'corr':** +% use correlation matrix of images instead of covariance. +% Insensitive to differences in scale and mean of data, and thus more +% sensitive to changes in pattern across image. % -% Examples: -% ---------------------------------------------------------------------- -% [ds, expectedds, p, wh_outlier_uncorr, wh_outlier_corr] = mahal(fmridat, 'noplot'); +% **'noplot':** +% Suppress the diagnostic plot of observed vs. expected +% Mahalanobis distances. % -% Y = ds - expectedds; -% wh = p < (.05 ./ length(p)); % Outliers after Bonferroni correction +% **'noverbose':** +% Suppress verbose console output. % -% plot(Y); -% plot(find(wh), Y(wh), 'ro', 'MarkerSize', 6); +% :Outputs: % -% Tor Wager +% **D2:** +% Vector of Mahalanobis distances (one per image). % +% **D2_expected:** +% Expected D2 values under the null distribution. % - -% Programmers' notes: tor - 4/6/2018, changed num of components retained to -% avoid empty components, evaluated as better performance against cov -% plots. Also added 'corr' mode to work on correlation rather than cov -% matrix [optional] +% **pval:** +% p-values for each image's Mahalanobis distance. +% +% **wh_outlier_uncorr:** +% Logical vectors of which images have mahalanobis distances with p < .05 +% and values greater than the median +% +% **wh_outlier_corr:** +% Logical vectors of which images have mahalanobis distances with p < .05 +% corrected (Bonferroni), and values greater than the median +% +% :Examples: +% :: +% +% [ds, expectedds, p, wh_outlier_uncorr, wh_outlier_corr] = mahal(fmridat, 'noplot'); +% +% Y = ds - expectedds; +% wh = p < (.05 ./ length(p)); % Outliers after Bonferroni correction +% +% plot(Y); +% plot(find(wh), Y(wh), 'ro', 'MarkerSize', 6); +% +% :See also: +% - multivar_dist +% - pca +% - qc_metrics_second_level +% +% .. +% Tor Wager +% +% Programmers' notes: +% tor - 4/6/2018, changed num of components retained to avoid empty +% components, evaluated as better performance against cov plots. Also +% added 'corr' mode to work on correlation rather than cov matrix +% [optional] +% .. doplot = true; doverbose = true; diff --git a/CanlabCore/@image_vector/mean.m b/CanlabCore/@image_vector/mean.m index c8ffa3c7..ee822fb5 100644 --- a/CanlabCore/@image_vector/mean.m +++ b/CanlabCore/@image_vector/mean.m @@ -1,28 +1,65 @@ function [m, varargout] = mean(obj, varargin) -% Mean across a set of images. Returns a new image_vector object. -% Creates an image_vector object with mean values for each voxel (cols) -% across images (rows) of an image_vector (e.g., fmri_data) object. +% mean Mean across a set of images, returned as a new image_vector object. +% +% Creates an image_vector object with mean values for each voxel (rows) +% across images (columns) of an image_vector (e.g., fmri_data) object. +% +% - Averages available valid data in each voxel. Some images may have +% missing data for some voxels. +% - Treats values of 0 as missing (after SPM) and excludes from mean, +% unless 'treat_zero_as_data' is passed. % % :Usage: % :: % -% function [m, imagemeans, voxelmeans] = mean(obj, [optional args]) +% [m, imagemeans, voxelmeans] = mean(obj, [optional args]) % -% m is an image_vector object whose data contains the mean values. +% :Inputs: % -% - Average available valid data in each voxel. Some images may have -% missing data for some voxels. -% - Treats values of 0 as missing (after SPM) and excludes from mean. +% **obj:** +% An image_vector / fmri_data / fmri_data_st object with one or +% more images stored as columns of .dat. % % :Optional Inputs: -% - 'group', 'group_by' -> followed by vector of integers to define -% groups (e.g., images from the same participant). Averages across images -% within each group. -% - 'write', followed by file name -% - 'path', followed by location for file (default = current directory) -% - 'orthviews' -> show orthviews for this image, same as orthviews(m) -% - 'histogram' -> show histogram for this image, same as histogram(m) -% - 'plot' -> do both +% +% **'group', 'group_by':** +% followed by vector of integers to define groups (e.g., images +% from the same participant). Averages across images within each +% group. +% +% **'write':** +% followed by file name (writes the mean image to disk) +% +% **'path':** +% followed by location for file (default = current directory) +% +% **'orthviews':** +% show orthviews for this image, same as orthviews(m) +% +% **'histogram':** +% show histogram for this image, same as histogram(m) +% +% **'plot':** +% do both ('orthviews' and 'histogram') +% +% **'treat_zero_as_data':** +% If supplied, zero values are kept as data (not converted to NaN +% before averaging). Default: false (zeros are treated as missing). +% +% :Outputs: +% +% **m:** +% An image_vector / fmri_data / fmri_data_st object whose data +% contains the mean values across images (or per-group means if +% group_by was specified). For fmri_data inputs, key fields +% (images_per_session, Y, Y_names, covariates, additional_info, +% metadata_table, etc.) are copied or group-averaged. +% +% **imagemeans (varargout{1}):** +% Optional. The mean of each image (i.e., column means of obj.dat). +% +% **voxelmeans (varargout{2}):** +% Optional. The mean of each voxel across images (row means). % % :Examples: % :: @@ -30,11 +67,17 @@ % % If sdat is an fmri_data object with multiple images, % m = mean(sdat, 'plot', 'write', anatmeanname, 'path', maskdir); % - -% Programmers' notes: -% - Tor: Average available valid data in each voxel (June 2017) -% - Michael Sun, PhD: Zero may actually be data, so NaN-ing them can be -% destructive, so add a flag 'treat_zero_as_data' 04/21/2025 +% :See also: +% - histogram +% - orthviews +% - write +% +% .. +% Programmers' notes: +% - Tor: Average available valid data in each voxel (June 2017) +% - Michael Sun, PhD: Zero may actually be data, so NaN-ing them can be +% destructive, so add a flag 'treat_zero_as_data' 04/21/2025 +% .. fname = []; fpath = pwd; diff --git a/CanlabCore/@image_vector/minus.m b/CanlabCore/@image_vector/minus.m deleted file mode 100644 index 99768c61..00000000 --- a/CanlabCore/@image_vector/minus.m +++ /dev/null @@ -1,31 +0,0 @@ -function c = minus(obj1,obj2) -% Implements the minus (-) operator on image_vector objects across voxels. -% Requires that each object has an equal number of columns and voxels -% -% .. -% Programmer Notes: -% Created 3/14/14 by Luke Chang -% .. - -error('This method is deprecated. Use image_math instead.'); - -%Check if image_vector object -if ~isa(obj1,'image_vector') || ~isa(obj2,'image_vector') - error('Input Data is not an image_vector object') -end - -%Check number of rows -if size(obj1.dat,1)~=size(obj2.dat,1) - error('number of voxels is different between objects.') -end - -%Check number of columns -if size(obj1.dat,2)~=size(obj2.dat,2) - error('number of observations is different between objects.') -end - -c = obj1; -c.dat = obj1.dat - obj2.dat; - - -end % function diff --git a/CanlabCore/@image_vector/montage.m b/CanlabCore/@image_vector/montage.m index 172c48c0..8964550a 100644 --- a/CanlabCore/@image_vector/montage.m +++ b/CanlabCore/@image_vector/montage.m @@ -1,39 +1,42 @@ function fig_handle = montage(image_obj, varargin) -% Create a montage of an image_vector (or statistic_image or fmri_data) -% object, on top of a standard anatomical underlay. +% montage Create a montage of an image_vector (or statistic_image or fmri_data) object. +% +% Renders blobs from an image_vector, statistic_image, or fmri_data object +% on top of a standard anatomical underlay. +% % - Takes optional inputs to fmridisplay.addblobs (see help for options) % - Max 4 images in object. Use get_wh_image to select images from object if needed. % -% *Usage: +% :Usage: % :: % % [fig_handle or o2 fmridisp object] = montage(image_obj, [optional arguments]) % -% :Optional inputs: +% :Inputs: +% +% **image_obj:** +% An image_vector, statistic_image, or fmri_data object containing +% up to four images to render as blobs. +% +% :Optional Inputs: +% % **fmridisplay:** % for fmridisplay object style montage [default] % % **scnmontage:** % for circa 2008-style SCN lab montage for each image vector % -% :Examples: -% :: -% -% o2 = montage(mask); -% -% o2 = montage(dat, 'trans'); -% o2 = montage(dat, 'trans', 'color', [1 0 0]); -% o2 = montage(dat, 'trans', 'color', [1 0 0], 'transvalue', .4); -% o2 = montage(dat, 'trans', 'maxcolor', [1 .3 0], 'mincolor', [.5 0 1]); -% o2 = montage(dat, 'trans', 'mincolor', [.5 0 1], 'transvalue', .5); +% **'targetsurface', name:** +% Pass a surface name through to addblobs (e.g., 'fsLR_32k'). % -% Options for canlab_results_fmridisplay are also passed forward, so that -% different named montage types can be used. E.g.,: +% **'sourcespace', name:** +% Pass a source space name through to addblobs. % -% o2 = montage(t, 'trans', 'full'); +% Options for canlab_results_fmridisplay are passed forward, so different +% named montage types can be used. E.g.: % % 'full' Axial, coronal, and saggital slices, 4 cortical surfaces -% 'compact' Midline saggital and two rows of axial slices [the default] +% 'compact' Midline saggital and two rows of axial slices [the default] % 'compact2' A single row showing midline saggital and axial slices % 'compact3' One row of axial slices, midline sagg, and 4 HCP surfaces % 'multirow' A series of 'compact2' displays in one figure for comparing different images/maps side by side @@ -41,22 +44,46 @@ % 'full2' for a slightly less full montage that avoids colorbar overlap issues % 'full hcp' for full montage, but with surfaces and volumes from HCP data % -% See help canlab_results_fmridisplay for more info and options +% See help canlab_results_fmridisplay for more info and options. % -% %% ========== Legend control -% There is a 'nolegend' option. -% Colorbar legends are created in render_on_surface -% You can access and control the handles like this: -% set(obj.activation_maps{1}.legendhandle, 'Position', [[0.965 0.0994 0.01 0.4037]]); +% Legend control: There is a 'nolegend' option. Colorbar legends are +% created in render_on_surface. You can access and control the handles +% like this: +% set(obj.activation_maps{1}.legendhandle, 'Position', [[0.965 0.0994 0.01 0.4037]]); +% +% Colormap range control: Range is set automatically by default, and +% stored in obj.activation_maps{wh_to_display}.cmaprange. You can enter +% 'cmaprange', followed by inputs in the correct format, to manually +% control this. +% +% :Outputs: +% +% **fig_handle:** +% For 'fmridisplay' style: the fmridisplay object (o2). For +% 'scnmontage' style: a vector of figure handles, one per image. +% +% :Examples: +% :: +% +% o2 = montage(mask); +% +% o2 = montage(dat, 'trans'); +% o2 = montage(dat, 'trans', 'color', [1 0 0]); +% o2 = montage(dat, 'trans', 'color', [1 0 0], 'transvalue', .4); +% o2 = montage(dat, 'trans', 'maxcolor', [1 .3 0], 'mincolor', [.5 0 1]); +% o2 = montage(dat, 'trans', 'mincolor', [.5 0 1], 'transvalue', .5); +% +% o2 = montage(t, 'trans', 'full'); % -% %% ========== Colormap range control -% Range is set automatically by default, and stored in -% obj.activation_maps{wh_to_display}.cmaprange -% You can enter 'cmaprange', followed by inputs in the correct format, to -% manually control this. +% % Set all color maps to the same range: +% o2 = montage(dat, 'trans', 'mincolor', [.5 0 1], 'transvalue', .7, 'cmaprange', [0 3]); % -% Set all color maps to the same range: -% o2 = montage(dat, 'trans', 'mincolor', [.5 0 1], 'transvalue', .7, 'cmaprange', [0 3]); +% :See also: +% - canlab_results_fmridisplay +% - fmridisplay +% - addblobs +% - render_on_surface +% - get_wh_image meth = 'fmridisplay'; diff --git a/CanlabCore/@image_vector/orthviews.m b/CanlabCore/@image_vector/orthviews.m index e36bdac2..26f6131f 100644 --- a/CanlabCore/@image_vector/orthviews.m +++ b/CanlabCore/@image_vector/orthviews.m @@ -1,12 +1,23 @@ function cl = orthviews(image_obj, varargin) -% Orthviews display (SPM) for CANlab image_vector (or fmri_data, statistic_image) object +% orthviews Display SPM orthviews for a CANlab image_vector / fmri_data / statistic_image object. % -% *Usage: +% Renders one or more images from a CANlab image_vector-derived object in +% SPM orthviews, with options for posneg coloring, unique-color parcel +% display, continuous color mapping, custom underlays, and adding to +% existing orthviews windows. +% +% :Usage: % :: % -% orthviews(image_obj, varargin) +% cl = orthviews(image_obj, [optional inputs]) +% +% :Inputs: +% +% **image_obj:** +% An image_vector, fmri_data, statistic_image, or atlas object. % % :Optional Inputs: +% % **'posneg':** % input generates orthviews using solid colors, separated for positive- and negative-valued voxels. % @@ -47,6 +58,26 @@ % e.g., 'color', {[1 0 1]} % This is superseded by 'unique' and 'posneg' options. % +% :Outputs: +% +% **cl:** +% A cell array of cluster / region structures used to draw the +% orthviews blobs (one cell per image). +% +% :Examples: +% :: +% +% orthviews(dat); +% orthviews(dat, 'posneg'); +% orthviews(t, 'unique'); +% orthviews(dat, 'overlay', which('keuken_2014_enhanced_for_underlay.img')); +% +% :See also: +% - spm_orthviews +% - canlab_get_underlay_image +% - region +% - montage +% % .. % Copyright Tor Wager, 2011 % .. diff --git a/CanlabCore/@image_vector/outliers.m b/CanlabCore/@image_vector/outliers.m index 61baca67..1fd2b24f 100644 --- a/CanlabCore/@image_vector/outliers.m +++ b/CanlabCore/@image_vector/outliers.m @@ -68,6 +68,11 @@ % :Examples: % :: % % ------------------------------------------------------------------------- +% % A minimal example on person-level (non-time series) image data +% obj = load_image_set('emotionreg'); +% [est_outliers_uncorr, est_outliers_corr, outlier_tables] = outliers(obj, 'notimeseries'); +% +% % ------------------------------------------------------------------------- % % Load a multi-study dataset, rescale it, and identify/plot outliers % % Use 'notimeseries' option because this is not a time series dataset % diff --git a/CanlabCore/@image_vector/pattern_surf_plot_mip.m b/CanlabCore/@image_vector/pattern_surf_plot_mip.m index e7993537..28baa465 100644 --- a/CanlabCore/@image_vector/pattern_surf_plot_mip.m +++ b/CanlabCore/@image_vector/pattern_surf_plot_mip.m @@ -1,21 +1,67 @@ function [mip, x, y, voldata] = pattern_surf_plot_mip(m, varargin) +% pattern_surf_plot_mip Axial maximum intensity projection pattern surface plot. % -% axial maximum intensity projection pattern surface plot -% needs documentation +% Reconstructs the image_vector data into a 3-D volume, optionally smooths +% it with a 3-D Gaussian, takes the axial (Z) maximum intensity +% projection, trims empty borders, and renders the result as both a 2-D +% image and a 3-D surface plot. % -% voldata is not used, just an output for other processes, e.g., rigid-body -% transformation +% :Usage: +% :: % -% m : image_vector (e.g., fmri_data or statistic_image) object +% [mip, x, y, voldata] = pattern_surf_plot_mip(m, [optional inputs]) % -% Optional inputs: -% 'nofigure', dofigure = false; -% 'nosmooth', dosmooth = false; -% 'smoothbox', mysmoothbox = varargin{i + 1}; varargin{i + 1} = []; -% 'sd', mygaussstd = varargin{i + 1}; varargin{i + 1} = []; +% :Inputs: % -% Tor Wager, 2016 -% Update: Aug 2016 +% **m:** +% image_vector (e.g., fmri_data or statistic_image) object. +% +% :Optional Inputs: +% +% **'nofigure':** +% Skip the figure creation / plotting step. +% +% **'nosmooth':** +% Skip 3-D Gaussian smoothing of the volume (default: smoothing on). +% +% **'smoothbox':** +% Smoothing kernel size (passed to smooth3). Default: 5. +% +% **'sd':** +% Gaussian SD for smoothing (passed to smooth3). Default: 2. +% +% **'xlim' / 'ylim' / 'zlim':** +% Spatial limits (currently parsed; reserved for future use). +% +% **'noverbose':** +% Suppress verbose output. +% +% :Outputs: +% +% **mip:** +% The maximum intensity projection (Y-by-X) with empty borders +% trimmed and out-of-mask voxels set to NaN. +% +% **x:** +% Vector of x mm coordinates corresponding to mip columns. +% +% **y:** +% Vector of y mm coordinates corresponding to mip rows. +% +% **voldata:** +% The trimmed (and optionally smoothed) 3-D volume reconstructed +% from m. Not used by this function but returned for downstream +% operations such as rigid-body transformation. +% +% :See also: +% - reconstruct_image +% - smooth3 +% - voxel2mm +% +% .. +% Tor Wager, 2016 +% Update: Aug 2016 +% .. % ------------------------------------------------------ % parse inputs diff --git a/CanlabCore/@image_vector/plot_current_orthviews_coord.m b/CanlabCore/@image_vector/plot_current_orthviews_coord.m index 05cfb1b8..6bf3924d 100644 --- a/CanlabCore/@image_vector/plot_current_orthviews_coord.m +++ b/CanlabCore/@image_vector/plot_current_orthviews_coord.m @@ -1,6 +1,39 @@ function voxel_data_series = plot_current_orthviews_coord(dat) -% Retrieves and plots the image data series at the current crosshairs in spm_orthviews +% plot_current_orthviews_coord Retrieve and plot the image data series at the current SPM orthviews crosshairs. % +% Looks up the voxel under the current spm_orthviews crosshair, finds its +% row in the image_vector data, and plots the values across images +% (e.g., across subjects, contrasts, or time points). If the voxel is +% outside the in-mask voxel list, prints a notice in the figure. +% +% :Usage: +% :: +% +% voxel_data_series = plot_current_orthviews_coord(dat) +% +% :Inputs: +% +% **dat:** +% An image_vector / fmri_data / statistic_image object with a +% valid .volInfo and .xyzlist. +% +% :Outputs: +% +% **voxel_data_series:** +% A 1 x n_images vector of values from dat.dat at the orthviews +% coordinate, or [] if the coordinate is outside the in-mask +% voxels or no valid volInfo is available. +% +% :Examples: +% :: +% +% orthviews(dat); +% y = plot_current_orthviews_coord(dat); +% +% :See also: +% - orthviews +% - spm_orthviews +% - mm2voxel voxel_data_series = []; diff --git a/CanlabCore/@image_vector/plus.m b/CanlabCore/@image_vector/plus.m deleted file mode 100644 index 0e37793b..00000000 --- a/CanlabCore/@image_vector/plus.m +++ /dev/null @@ -1,36 +0,0 @@ -function c = plus(obj1,obj2) -% Implements the plus (+) operator on image_vector objects across voxels. -% Requires that each object has an equal number of columns and voxels -% -% :Examples: -% :: -% -% c = dat1 + dat2; -% -% .. -% Programmer Notes: -% Created 3/14/14 by Luke Chang -% .. - -error('This method is deprecated. Use image_math instead.'); - -%Check if image_vector object -if ~isa(obj1,'image_vector') || ~isa(obj2,'image_vector') - error('Input Data is not an image_vector object') -end - -%Check number of rows -if size(obj1.dat,1)~=size(obj2.dat,1) - error('number of voxels is different between objects.') -end - -%Check number of columns -if size(obj1.dat,2)~=size(obj2.dat,2) - error('number of observations is different between objects.') -end - -c = obj1; -c.dat = obj1.dat + obj2.dat; - - -end % function diff --git a/CanlabCore/@image_vector/power.m b/CanlabCore/@image_vector/power.m deleted file mode 100644 index c9965ea0..00000000 --- a/CanlabCore/@image_vector/power.m +++ /dev/null @@ -1,26 +0,0 @@ -function c = power(obj, b) -% Implements the power (^) operator on image_vector objects across voxels. -% -% :Examples: -% :: -% -% c = dat1^2; -% -% .. -% Programmer Notes: -% Created 3/14/14 by Luke Chang -% .. - -error('This method is deprecated. Use image_math instead.'); - - -%Check if image_vector object -if ~isa(obj,'image_vector') - error('Input Data is not an image_vector object') -end - -c = obj; -c.dat = obj.dat.^b; - - -end % function diff --git a/CanlabCore/@image_vector/prctile.m b/CanlabCore/@image_vector/prctile.m index 9f4a055b..643cf700 100644 --- a/CanlabCore/@image_vector/prctile.m +++ b/CanlabCore/@image_vector/prctile.m @@ -1,16 +1,46 @@ function threshold_values = prctile(obj, percentile_values) -% Cumulative distribution: Calculate thresholds in data values for a series of percentile values you specify -% - percentile_values(i) % of data values are at or below threshold_values(i) -% - Thresholds for percentiles are estimated across the entire image matrix (4-D) -% - Excludes zero-valued elements of matrix +% prctile Calculate thresholds in image data values for specified percentile values. % -% Example: +% Cumulative distribution: percentile_values(i) % of data values are at +% or below threshold_values(i). Thresholds for percentiles are estimated +% across the entire image matrix (4-D). Excludes zero-valued elements of +% the matrix. % -% perc_vals = [.1 1 5 95 99 99.9]; -% obj = load_image_set('emotionreg'); -% thr = prctile(obj, perc_vals); +% :Usage: +% :: % -% Tor Wager, May 2018 +% threshold_values = prctile(obj, percentile_values) +% +% :Inputs: +% +% **obj:** +% An image_vector / fmri_data / statistic_image object. +% +% **percentile_values:** +% Vector of percentile values (0-100) at which to compute +% thresholds. +% +% :Outputs: +% +% **threshold_values:** +% Vector of data thresholds, the same length as +% percentile_values, such that percentile_values(i) % of data +% values are at or below threshold_values(i). +% +% :Examples: +% :: +% +% perc_vals = [.1 1 5 95 99 99.9]; +% obj = load_image_set('emotionreg'); +% thr = prctile(obj, perc_vals); +% +% :See also: +% - prctile +% - threshold +% +% .. +% Tor Wager, May 2018 +% .. mydat = double(obj.dat(:)); diff --git a/CanlabCore/@image_vector/preprocess.m b/CanlabCore/@image_vector/preprocess.m index 3594cdff..ae7e083a 100644 --- a/CanlabCore/@image_vector/preprocess.m +++ b/CanlabCore/@image_vector/preprocess.m @@ -1,13 +1,24 @@ function [obj, varargout] = preprocess(obj, meth, varargin) -% Preprocesses data in an image_vector (e.g., fmri_data) object; many options for filtering and outlier id +% preprocess Preprocess data in an image_vector (e.g., fmri_data) object: filtering and outlier id. % -% Data is observations (i.e., voxels, subjects) x images, so operating on the columns operates on -% images, and operating on the rows operates on voxels (or variables more -% generally) across images. +% Data is observations (i.e., voxels, subjects) x images, so operating +% on the columns operates on images, and operating on the rows operates +% on voxels (or variables more generally) across images. +% +% :Usage: +% :: +% +% [obj, varargout] = preprocess(obj, meth, [optional inputs]) % % :Inputs: % -% **meth:** Options +% **obj:** +% An image_vector / fmri_data object. +% +% **meth:** +% String specifying the preprocessing method. See :Methods: below. +% +% :Methods: % % **resid:** % Residualize voxels with respect to covariates @@ -47,9 +58,9 @@ % [obj, rmssd, wh_outliers_rmssd] = preprocess(obj, 'outliers_rmssd'); % % **smooth:** -% Smoothed images with Gaussian filter -% - obj = preprocess(obj, 'smooth', FWHM in mm) -% - Enter smoothing kernel in mm, as a scalar or [sx sy sz] triple +% Smoothed images with Gaussian filter +% - obj = preprocess(obj, 'smooth', FWHM in mm) +% - Enter smoothing kernel in mm, as a scalar or [sx sy sz] triple % % **interp_images:** % Interpolate all voxels in a series of images specified @@ -59,10 +70,10 @@ % **remove_white_csf:** % Note: Enter 'components' keyword to use 1st 5 comps of WM and CSF, % or no keyword to use only WM and CSF mean global signal. -% Extract data values for gray, white, CSF -% Regress gray matter mean on first 5 components of white-matter and CSF -% Remove the fitted values from the images image-wise -% This adjusts for variations in overall image intensity that are explainable by variations in white-matter and CSF +% Extract data values for gray, white, CSF. +% Regress gray matter mean on first 5 components of white-matter and CSF. +% Remove the fitted values from the images image-wise. +% This adjusts for variations in overall image intensity that are explainable by variations in white-matter and CSF. % By default, uses a highly eroded standard mask for gray/white/CSF, % that avoids mixing signal components coming from gray matter. % Requires that images be registered in MNI space to work @@ -70,37 +81,58 @@ % This effectively removes a scalar multiple of each white/CSF regressor from % all voxels in each image set. % Estimating parameters for each voxel independently would add a lot of -% variability. this way, we estimate the overall location of the image +% variability. This way, we estimate the overall location of the image % (shift up/down from zero in gray matter) that is predictable from % gray/white variables, and remove that. -% we can apply this to any data - time series, contrast images, beta +% We can apply this to any data - time series, contrast images, beta % images, signature response values. % % **remove_csf:** -% Same as remove_white_csf, but use only CSF predictors +% Same as remove_white_csf, but use only CSF predictors. % This is because work, e.g., that of John Gore, suggests there is likely % real signal in WM, so removing it can cause real signal to be -% removed +% removed. % Note: Enter 'components' keyword to use 1st 5 comps of CSF, % or no keyword to use only CSF mean global signal. % % **rescale_by_csf:** -% This attempts to model and remove scale inhomogeneity across -% images. It estimates the relationship between the spatial median abs. -% deviation (MAD) for CSF voxels and for GM voxels. The proportion of the GM deviation -% for each image fitted by CSF is divided out. +% This attempts to model and remove scale inhomogeneity across +% images. It estimates the relationship between the spatial median abs. +% deviation (MAD) for CSF voxels and for GM voxels. The proportion of the GM deviation +% for each image fitted by CSF is divided out. +% +% :Optional Inputs: +% +% **'plot':** +% For 'outliers' / 'outliers_rmssd' / 'remove_white_csf' / +% 'remove_csf' / 'rescale_by_csf', creates diagnostic plots. +% +% **'components':** +% For 'remove_white_csf' / 'remove_csf', use top 5 components of +% WM/CSF instead of mean global signal. +% +% :Outputs: +% +% **obj:** +% Modified image_vector / fmri_data object with the requested +% preprocessing applied. obj.history is updated to reflect the +% operation. +% +% **varargout:** +% For 'outliers_rmssd', varargout{1} = rmssd vector and +% varargout{2} = logical outlier vector. % % :Examples: % :: % -% % two complementary ways to get and plot outliers: -% --------------------------------------------------------------------- +% % two complementary ways to get and plot outliers: +% % --------------------------------------------------------------------- % dat = preprocess(dat, 'outliers', 'plot'); % subplot(5, 1, 5); % go to new panel... % dat = preprocess(dat, 'outliers_rmssd', 'plot'); % -% Concatenate a set of image objects and then regress out white/CSF components -% --------------------------------------------------------------------- +% % Concatenate a set of image objects and then regress out white/CSF components +% % --------------------------------------------------------------------- % DATA_CAT = cat(DATA_OBJ{:}); % for i = 1:size(DATA_OBJ, 2), sz(i) = size(DATA_OBJ{i}.dat, 2); end % DATA_CAT.images_per_session = sz; @@ -108,14 +140,22 @@ % % DATA_CAT = preprocess(DATA_CAT, 'remove_white_csf'); % -% For a 2nd-level image dataset, Regress out and rescale by CSF values -% Plot before and after -% --------------------------------------------------------------------- -% obj = load_image_set('emotionreg'); -% histogram(obj, 'by_tissue_type', 'byimage'); -% obj = preprocess(obj, 'remove_csf'); -% obj = preprocess(obj, 'rescale_by_csf'); -% history(obj) +% % For a 2nd-level image dataset, regress out and rescale by CSF values. +% % Plot before and after +% % --------------------------------------------------------------------- +% obj = load_image_set('emotionreg'); +% histogram(obj, 'by_tissue_type', 'byimage'); +% obj = preprocess(obj, 'remove_csf'); +% obj = preprocess(obj, 'rescale_by_csf'); +% history(obj) +% +% :See also: +% - extract_gray_white_csf +% - hpfilter +% - mahal +% - intercept_model +% - iimg_smooth_3d +% - canlab_connectivity_preproc switch meth diff --git a/CanlabCore/@image_vector/read_from_file.m b/CanlabCore/@image_vector/read_from_file.m index 9635ea34..21085f31 100644 --- a/CanlabCore/@image_vector/read_from_file.m +++ b/CanlabCore/@image_vector/read_from_file.m @@ -1,13 +1,42 @@ function obj = read_from_file(obj) -% Reads data from image filenames into obj.dat +% read_from_file Read image data from disk into the .dat field of an image_vector object. % -% Try obj = check_image_filenames(obj) first. +% Loads voxel data from the files listed in obj.fullpath into obj.dat. +% If obj.volInfo is empty, it is created from the first image. Try +% check_image_filenames(obj) first to make sure obj.fullpath points to +% valid files on disk. % -% This is automatically called if you create a new image_vector object with -% names but do not directly enter data. e.g., the commands below will load data: -% - name = 'salientmap.nii'; -% - img = image_vector('image_names', name); -% +% This is automatically called if you create a new image_vector object +% with names but do not directly enter data. +% +% :Usage: +% :: +% +% obj = read_from_file(obj) +% +% :Inputs: +% +% **obj:** +% An image_vector / fmri_data object with .fullpath populated. +% If .volInfo is empty, it will be created from the first image. +% +% :Outputs: +% +% **obj:** +% The input object with .dat populated as single-precision values +% and .volInfo populated if it was empty. +% +% :Examples: +% :: +% +% name = 'salientmap.nii'; +% img = image_vector('image_names', name); +% % read_from_file is called automatically inside the constructor. +% +% :See also: +% - check_image_filenames +% - iimg_read_img +% - iimg_get_data % disp('Reading image data into object .dat field.'); @@ -21,16 +50,12 @@ % Now extract the actual data from the mask switch spm('Ver') - - case {'SPM8', 'SPM5','SPM12', 'SPM25'} - obj.dat = iimg_get_data(obj.volInfo, obj.fullpath, 'single', 'noexpand')'; - case {'SPM2', 'SPM99'} - % legacy, for old SPM + % legacy SPM obj.dat = iimg_get_data(obj.volInfo, obj.fullpath, 'single')'; - otherwise - error('Unknown version of SPM! Update code, check path, etc.'); + % SPM5+, including any future versions + obj.dat = iimg_get_data(obj.volInfo, obj.fullpath, 'single', 'noexpand')'; end diff --git a/CanlabCore/@image_vector/rebuild_volinfo_from_dat.m b/CanlabCore/@image_vector/rebuild_volinfo_from_dat.m index a85f56f0..da7d433a 100644 --- a/CanlabCore/@image_vector/rebuild_volinfo_from_dat.m +++ b/CanlabCore/@image_vector/rebuild_volinfo_from_dat.m @@ -1,10 +1,17 @@ function dat = rebuild_volinfo_from_dat(dat, newdat) -% Will rebuild volInfo (the image space, or sometimes "mask") from a vectorized image. -% In other words, will rebuild dat.volInfo from newdat. +% rebuild_volinfo_from_dat Rebuild volInfo from a full-image vector and reset .dat to non-zero values. % -% Also resets all voxels to be significant, if a statistic image +% Will rebuild volInfo (the image space, or sometimes "mask") from a +% vectorized image. In other words, will rebuild dat.volInfo from newdat. % -% :Input: +% Also resets all voxels to be significant, if a statistic image. +% +% :Usage: +% :: +% +% dat = rebuild_volinfo_from_dat(dat, newdat) +% +% :Inputs: % % **dat:** % an image_vector @@ -12,11 +19,21 @@ % **newdat:** % a vector that MUST be size of ENTIRE image (dat.volInfo.nvox) % -% :Output: +% :Outputs: % % **dat:** % dat.dat contains the non-zero values of newdat, and dat.volInfo is % correctly defining the image space +% +% :Examples: +% :: +% +% dat = rebuild_volinfo_from_dat(dat, newvec); +% +% :See also: +% - reconstruct_image +% - reparse_contiguous +% - replace_empty if length(newdat) ~= dat.volInfo.nvox diff --git a/CanlabCore/@image_vector/reconstruct_image.m b/CanlabCore/@image_vector/reconstruct_image.m index 0f3f05e5..bc55130b 100644 --- a/CanlabCore/@image_vector/reconstruct_image.m +++ b/CanlabCore/@image_vector/reconstruct_image.m @@ -1,39 +1,65 @@ function [voldata, vectorized_voldata, xyz_coord_struct] = reconstruct_image(obj) -% Reconstruct a 3-D or 4-D image from image_vector object obj +% reconstruct_image Reconstruct a 3-D or 4-D image volume from an image_vector object. % -% [voldata, vectorized_voldata, xyz_coord_struct] = reconstruct_image(obj) +% Reconstructs a full 3-D (or 4-D, for multi-image objects) volume from +% the in-mask voxel list in obj.dat / obj.volInfo. The output has one +% element for every voxel in THE ENTIRE IMAGE, and so can be very +% memory-intensive. But it's useful for lining up voxels across images +% with different masks / in-mask voxels. % -% voldata is and X x Y x Z x Images matrix -% vectorized_voldata is the same, with all voxels vectorized +% This function returns output in memory; see image_vector.write for +% writing .img files to disk. % -% This output has one element for every voxel in THE ENTIRE IMAGE, and so -% can be very memory-intensive. But it's useful for lining up voxels -% across images with different masks/in-mask voxels. +% :Usage: +% :: % -% This function returns output in memory; -% see image_vector.write for writing .img files to disk. +% [voldata, vectorized_voldata, xyz_coord_struct] = reconstruct_image(obj) +% +% :Inputs: +% +% **obj:** +% image_vector / fmri_data / statistic_image object. For +% statistic_image objects, .dat is multiplied by .sig before +% reconstruction. % % :Outputs: % % **voldata:** -% 3-D recon volume +% 3-D recon volume (X x Y x Z x Images matrix). +% % **vectorized_voldata:** -% volume in column vector, iimg_xxx function format +% volume in column vector, iimg_xxx function format. +% % **xyz_coord_struct:** % has fields with coordinate information in mm (world) space +% % - x, y, z : vectors of coordinates in mm for each of the 3 % dimensions of the image % - X, Y, Z : output matrices from meshgrid with mm coordinates, % for volume visualization. % These can be passed to surf or isocaps functions for volume % visualization in world space (mm). +% - voldata : volume rotated/flipped for compatibility with +% addbrain.m direction. +% +% :Examples: +% :: +% +% [vol, vec, S] = reconstruct_image(dat); +% figure; isosurface(S.X, S.Y, S.Z, S.voldata, 0.5); +% +% :See also: +% - iimg_reconstruct_vols +% - write +% - get_xyzmm_coordinates +% - isosurface % % .. % Copyright 2011 tor wager % % Programmers' notes: % -% Aug 2012: This function does not flip the data based on the sign of x dimension. +% Aug 2012: This function does not flip the data based on the sign of x dimension. % The flipping is applied in image writing / display in % iimg_reconstruct_vols, write method, spm_orthviews, etc. % diff --git a/CanlabCore/@image_vector/remove_empty.m b/CanlabCore/@image_vector/remove_empty.m index c93087f3..a522fad3 100644 --- a/CanlabCore/@image_vector/remove_empty.m +++ b/CanlabCore/@image_vector/remove_empty.m @@ -1,21 +1,52 @@ function dat = remove_empty(dat, varargin) -% remove vox: logical vector of custom voxels to remove, VOX x 1 +% remove_empty Remove empty (zero/NaN) voxels and images from an image_vector object. % -% remove im: logical vector of custom images to remove, 1 x IMAGES -% -% indices of removed data will be stored in removed_voxels and -% removed_images fields, to preserve ability to later reconstruct into 3D images +% Identifies empty voxels (rows whose values are all 0 or NaN across +% images) and empty images (columns whose values are all 0 or NaN +% across voxels), and removes them from .dat. Indices of removed data +% are stored in removed_voxels and removed_images fields, preserving the +% ability to later reconstruct into 3-D images. Optional logical vectors +% can specify additional custom voxels / images to remove. % % :Usage: % :: % % dat = remove_empty(dat, [logical vector of custom voxels to remove], [logical vector of imgs to remove]) % -% Indicator vectors stored in: -% removed_images -% removed_voxels +% :Inputs: +% +% **dat:** +% An image_vector / fmri_data / statistic_image / atlas object. +% +% :Optional Inputs: +% +% **remove_vox:** +% Logical vector of custom voxels to remove, VOX x 1. +% +% **remove_im:** +% Logical vector of custom images to remove, 1 x IMAGES. +% +% :Outputs: +% +% **dat:** +% The input object with empty (and any custom-specified) voxels +% and images removed from .dat. The .removed_voxels and +% .removed_images bookkeeping fields are updated. For +% statistic_image, the .p / .ste / .sig (and .N, .dfe if +% non-scalar) fields are pruned in parallel; for atlas, the +% .probability_maps are pruned. +% +% :Examples: +% :: +% +% dat = remove_empty(dat); +% dat = remove_empty(dat, my_voxels_to_remove); +% dat = remove_empty(dat, [], my_images_to_remove); % -% :See also: replace_empty +% :See also: +% - replace_empty +% - rebuild_volinfo_from_dat +% - reparse_contiguous % force logical dat.removed_images = logical(dat.removed_images); diff --git a/CanlabCore/@image_vector/render_on_cerebellar_flatmap.m b/CanlabCore/@image_vector/render_on_cerebellar_flatmap.m index b9704bfb..c917b051 100644 --- a/CanlabCore/@image_vector/render_on_cerebellar_flatmap.m +++ b/CanlabCore/@image_vector/render_on_cerebellar_flatmap.m @@ -1,17 +1,24 @@ function giftiname = render_on_cerebellar_flatmap(target_obj, varargin) -% Render an object with a single image onto a standard cerebellar flat map. +% render_on_cerebellar_flatmap Render an image onto a standard cerebellar flat map. +% +% Renders an object with a single image onto the Diedrichsen & Zotow +% (2015) cerebellar flat map using the SUIT toolbox. % % - requires SPM, the SUIT toolbox, and some standard templates on your Matlab path. % - saves a Gifti .gii image of your map, called .cblm_surf.gii % -% Diedrichsen, J. & Zotow, E. (2015). Surface-based display of volume-averaged cerebellar data. PLoS One, 7, e0133402. -% https://www.diedrichsenlab.org/imaging/suit_flatmap.htm +% :Usage: +% :: +% +% giftiname = render_on_cerebellar_flatmap(target_obj, [optional inputs]) % % :Inputs: % +% **target_obj:** +% A CANlab fmri_data object you want to visualize. Must contain +% only a single image. +% % :Optional Inputs: -% **'target_obj':** -% A CANlab fmri_data object you want to visualize % % **'newfigure':** [numeric scalar] % Create a new figure; default = uses existing axes @@ -20,38 +27,61 @@ % An n-by-3 colormap matrix used for rendering. % Default = colormap_tor([0 0 1], [1 1 0], [0.5 0.5 0.5], [0 0.5 1], [1 0.5 0]). % -% Some examples: -% -% Pain-related activation from 2021 Spisak Placebo meta N = 603 maps -% fname = which('full_pain_g_pperm_FWE05.nii.gz'); -% pain603 = fmri_data(fname); -% render_on_cerebellar_flatmap(pain603) -% -% target_obj = load_image_set('nps'); -% render_on_cerebellar_flatmap(target_obj) -% -% transgrad = cifti_read('transcriptomic_gradients.dscalar.nii'); -% r = cifti_struct_2_region_obj(cifti_struct, 'which_image', 3); % 3rd gradient -% subctx_fmri_data_obj = region2fmri_data(r); -% render_on_cerebellar_flatmap(subctx_fmri_data_obj, 'color_map', colormap('summer')) -% render_on_cerebellar_flatmap(subctx_fmri_data_obj) - - -% Some other examples: -% fname = which('full_pla_g_pperm_tfce_FWE05.nii.gz'); -% placebo603 = fmri_data(fname); -% -% fname = which('full_pla_rrating_pperm_tfce_FWE05.nii.gz'); -% placebocorr603 = fmri_data(fname); -% -% gunzip(pain603.fullpath); -% pain603fname = strrep(pain603.fullpath, '.gz', ''); -% -% gunzip(placebo603.fullpath); -% placebo603fname = strrep(placebo603.fullpath, '.gz', ''); -% -% gunzip(placebocorr603.fullpath); -% placebocorr603fname = strrep(placebocorr603.fullpath, '.gz', ''); +% **'cscale':** [1 x 2 numeric] +% Color limits for the flat-map plot. Default: [-q95, q95] of +% absolute surface data. +% +% **'verbose':** [logical] +% Verbose output flag (default false). +% +% :Outputs: +% +% **giftiname:** +% Full path to the saved Gifti .gii image of the cerebellar +% surface map (.cblm_surf.gii). +% +% :Examples: +% :: +% +% % Pain-related activation from 2021 Spisak Placebo meta N = 603 maps +% fname = which('full_pain_g_pperm_FWE05.nii.gz'); +% pain603 = fmri_data(fname); +% render_on_cerebellar_flatmap(pain603) +% +% target_obj = load_image_set('nps'); +% render_on_cerebellar_flatmap(target_obj) +% +% transgrad = cifti_read('transcriptomic_gradients.dscalar.nii'); +% r = cifti_struct_2_region_obj(cifti_struct, 'which_image', 3); % 3rd gradient +% subctx_fmri_data_obj = region2fmri_data(r); +% render_on_cerebellar_flatmap(subctx_fmri_data_obj, 'color_map', colormap('summer')) +% render_on_cerebellar_flatmap(subctx_fmri_data_obj) +% +% % Some other examples: +% % fname = which('full_pla_g_pperm_tfce_FWE05.nii.gz'); +% % placebo603 = fmri_data(fname); +% % +% % fname = which('full_pla_rrating_pperm_tfce_FWE05.nii.gz'); +% % placebocorr603 = fmri_data(fname); +% % +% % gunzip(pain603.fullpath); +% % pain603fname = strrep(pain603.fullpath, '.gz', ''); +% % +% % gunzip(placebo603.fullpath); +% % placebo603fname = strrep(placebo603.fullpath, '.gz', ''); +% % +% % gunzip(placebocorr603.fullpath); +% % placebocorr603fname = strrep(placebocorr603.fullpath, '.gz', ''); +% +% :References: +% Diedrichsen, J. & Zotow, E. (2015). Surface-based display of +% volume-averaged cerebellar data. PLoS One, 7, e0133402. +% https://www.diedrichsenlab.org/imaging/suit_flatmap.htm +% +% :See also: +% - suit_reslice_dartel +% - suit_map2surf +% - suit_plotflatmap %% % ------------------------------------------------------------------------- diff --git a/CanlabCore/@image_vector/reparse_contiguous.m b/CanlabCore/@image_vector/reparse_contiguous.m index 76588026..cfecf907 100644 --- a/CanlabCore/@image_vector/reparse_contiguous.m +++ b/CanlabCore/@image_vector/reparse_contiguous.m @@ -1,21 +1,59 @@ function obj = reparse_contiguous(obj, varargin) -% Re-construct list of contiguous voxels in an image based on in-image -% voxel coordinates. Coordinates are taken from obj.volInfo.xyzlist. +% reparse_contiguous Re-build the list of contiguous voxels (clusters) in obj.volInfo.cluster. % -% Results are saved in obj.volInfo.cluster. +% Re-construct the list of contiguous voxels in an image based on +% in-image voxel coordinates. Coordinates are taken from +% obj.volInfo.xyzlist. Results are saved in obj.volInfo.cluster. % -% xyzlist can be generated from iimg_read_img, and is done automatically by -% object-oriented fMRI image classes (fmri_image, image_vector, -% statistic_image) +% xyzlist can be generated from iimg_read_img, and is done automatically +% by object-oriented fMRI image classes (fmri_image, image_vector, +% statistic_image). % % If 'nonempty' is entered as an optional argument, will use only voxels % that are non-zero, non-nan in all columns of obj.dat. % +% .cluster and .xyzlist should both always be length v in-mask voxels; +% if 'nonempty' is entered, then .dat should be length v in-mask voxels +% too. +% +% NOTES: this will only work if xyzlist in volInfo has only voxels for +% contiguous clusters. If the mask in volInfo contains the whole brain, +% use 'nonempty' to exclude empty data values when redefining clusters. +% % :Usage: % :: % % obj = reparse_contiguous(obj, ['nonempty']) % +% :Inputs: +% +% **obj:** +% An image_vector / fmri_data / statistic_image object with +% populated .volInfo.xyzlist. +% +% :Optional Inputs: +% +% **'nonempty':** +% Use only voxels that are non-zero, non-NaN in all columns of +% obj.dat when computing contiguous clusters. +% +% :Outputs: +% +% **obj:** +% The input object with obj.volInfo.cluster recomputed via +% spm_clusters. +% +% :Examples: +% :: +% +% obj = reparse_contiguous(obj); +% obj = reparse_contiguous(obj, 'nonempty'); +% +% :See also: +% - spm_clusters +% - region +% - replace_empty +% % .. % Copyright tor wager, 2011 % @@ -25,14 +63,6 @@ % also fixed bug - was not using 'nonempty' input in some % cases % .. -% -% .cluster and .xyzlist should both always be length v in-mask voxels -% if 'nonempty' is entered, then .dat should be length v in-mask voxels too -% -% NOTES: this will only work if xyzlist in volInfo has only voxels for -% contiguous clusters. If the mask in volInfo contains the whole brain, -% use 'nonempty' to exclude empty data values when -% redefining clusters. wh = true(obj.volInfo.n_inmask, 1); %size(obj.volInfo.cluster)); %obj.volInfo.wh_inmask; diff --git a/CanlabCore/@image_vector/replace_empty.m b/CanlabCore/@image_vector/replace_empty.m index 9168c611..7f5a48a6 100644 --- a/CanlabCore/@image_vector/replace_empty.m +++ b/CanlabCore/@image_vector/replace_empty.m @@ -1,25 +1,52 @@ function obj = replace_empty(obj, varargin) -% Replace empty/missing values in an image data object +% replace_empty Replace previously removed (empty) voxels / images in an image data object with zeros. +% +% Replace missing values in obj.dat stored in obj.removed_voxels and +% obj.removed_images with zeros. This returns obj.dat in a format that +% can be reconstructed into a 3-D or 4-D image matrix for brain +% visualization. For statistic_image objects, .p is filled with ones +% (so removed voxels are not flagged significant), and .ste / .sig / +% .N / .dfe are filled appropriately. For atlas objects, .probability_maps +% are zero-filled. % % :Usage: % :: % % obj = replace_empty(obj, [optional keywords]) % -% Replace missing values in obj.dat stored in obj.removed_voxels and -% obj.removed_images with zeros. This returns obj.dat in a format that can -% be reconstructed into a 3-D or 4-D image matrix for brain visualization. +% :Inputs: % -% :Optional keywords: +% **obj:** +% An image_vector / fmri_data / statistic_image / atlas object. +% +% :Optional Inputs: % % **'voxels' or 'images':** % replace only missing voxels/images % +% :Outputs: +% +% **obj:** +% The input object with previously removed voxels and/or images +% re-inserted (with zeros / ones as appropriate). The +% .removed_voxels and .removed_images fields are reset to all +% false for the dimensions that were filled in. +% +% :Examples: +% :: +% +% obj = replace_empty(dat); +% obj = replace_empty(dat, 'voxels'); +% +% :See also: +% - remove_empty +% - zeroinsert +% - nanremove +% - naninsert +% % .. % Tor Wager, 12/1/10 % .. -% -% :See also: remove_empty, zeroinsert, nanremove, naninsert dovoxels = 1; doimages = 1; diff --git a/CanlabCore/@image_vector/resample_space.m b/CanlabCore/@image_vector/resample_space.m index fb1e0b81..9b08cc74 100644 --- a/CanlabCore/@image_vector/resample_space.m +++ b/CanlabCore/@image_vector/resample_space.m @@ -1,26 +1,51 @@ function obj = resample_space(obj, sampleto, varargin) -% Resample the images in an fmri_data object (obj) to the space of another -% image (sampleto; e.g., a mask image). Works for all image_vector objects. -% The object includes only voxels in the in-mask region in the target -% (sampleto) image. +% resample_space Resample the images in an image_vector object to the space of another image. +% +% Resample the images in an fmri_data object (obj) to the space of +% another image (sampleto; e.g., a mask image). Works for all +% image_vector objects. The object includes only voxels in the in-mask +% region in the target (sampleto) image. % % :Usage: % :: % % obj = resample_space(obj, sampleto, [sampling method]) % -% Sampleto may be one of these: -% 1. a volInfo structure (the image does not have to exist on the path) -% 2. an image_vector, fmri_data, fmri_mask_image object -% 3. a string with the name of an image +% :Inputs: +% +% **obj:** +% An image_vector / fmri_data / statistic_image / atlas object +% to resample. +% +% **sampleto:** +% The target space, in any of these forms: +% +% 1. a volInfo structure (the image does not have to exist on the path) +% 2. an image_vector, fmri_data, fmri_mask_image object +% 3. a string with the name of an image +% +% :Optional Inputs: % -% Can enter resampling method as optional input. Takes any input to -% interp3: -% 'nearest' - nearest neighbor interpolation -% 'linear' - linear interpolation (default) -% 'spline' - spline interpolation -% 'cubic' - cubic interpolation as long as the data is uniformly -% spaced, otherwise the same as 'spline' +% **sampling method:** +% Can enter resampling method as optional input. Takes any input +% to interp3: +% +% - 'nearest' - nearest neighbor interpolation +% - 'linear' - linear interpolation (default) +% - 'spline' - spline interpolation +% - 'cubic' - cubic interpolation as long as the data is +% uniformly spaced, otherwise the same as 'spline' +% +% :Outputs: +% +% **obj:** +% The input object resampled into the target space. .volInfo is +% replaced with sampleto.volInfo, .dat is interpolated into the +% new space, .removed_voxels is updated, and clusters are +% re-parsed. For atlas objects, the .probability_maps and label +% index are rebuilt; for statistic_image, .p / .ste / .sig / .N +% are resampled appropriately (with .sig defaulting to nearest +% neighbor). % % :Examples: % :: @@ -28,6 +53,12 @@ % label_mask = fmri_data(which('atlas_labels_combined.img')); % label_mask = resample_space(label_mask, ivec, 'nearest') % resamples and masks label image % +% :See also: +% - resample_to_image_space +% - interp3 +% - reparse_contiguous +% - probability_maps_to_region_index +% % .. % Programmers' notes: % @@ -46,7 +77,7 @@ % to nearest neighbor, and add related warnings % 3/9/2026 Tor Wager changed sampling method to match SPM style, % correcting bug with 1-voxel shift in-plane, using affine transformation -% matrix T instead to map all voxels. +% matrix T instead to map all voxels. % .. n_imgs = size(obj.dat, 2); diff --git a/CanlabCore/@image_vector/resample_space_old.m b/CanlabCore/@image_vector/resample_space_old.m deleted file mode 100644 index f19a53e8..00000000 --- a/CanlabCore/@image_vector/resample_space_old.m +++ /dev/null @@ -1,493 +0,0 @@ -function obj = resample_space_old(obj, sampleto, varargin) -% Resample the images in an fmri_data object (obj) to the space of another -% image (sampleto; e.g., a mask image). Works for all image_vector objects. -% The object includes only voxels in the in-mask region in the target -% (sampleto) image. -% -% :Usage: -% :: -% -% obj = resample_space(obj, sampleto, [sampling method]) -% -% Sampleto may be one of these: -% 1. a volInfo structure (the image does not have to exist on the path) -% 2. an image_vector, fmri_data, fmri_mask_image object -% 3. a string with the name of an image -% -% Can enter resampling method as optional input. Takes any input to -% interp3: -% 'nearest' - nearest neighbor interpolation -% 'linear' - linear interpolation (default) -% 'spline' - spline interpolation -% 'cubic' - cubic interpolation as long as the data is uniformly -% spaced, otherwise the same as 'spline' -% -% :Examples: -% :: -% -% label_mask = fmri_data(which('atlas_labels_combined.img')); -% label_mask = resample_space(label_mask, ivec, 'nearest') % resamples and masks label image -% -% .. -% Programmers' notes: -% -% 1/27/2012 Tor edited to handle .mask field in fmri_data and .sig field in -% statistic_image. Was causing errors otherwise... -% Also changed automatic behavior to reparse contig voxels with -% 'nonempty' in output obj -% -% 2/5/2018 Tor added support for atlas objects - special handling -% -% 2/23/2018 Stephan changed replace_empty(obj) into replace_empty(obj,'voxels') to prevent adding removed images back in -% 5/18/2021 Tor removed line: obj_out = replace_empty(obj_out); for -% statistic_image objects, as it was causing a voxel mismatch...incorrect -% removed_voxels due to partially built object -% 10/28/2024 Zizhuang changed the default method to resample .sig field -% to nearest neighbor, and add related warnings -% .. - -n_imgs = size(obj.dat, 2); - -if ischar(sampleto) - sampleto = fmri_data(sampleto); -end - -Vto = sampleto.volInfo; -SPACEto = define_sampling_space(Vto, 1); - -Vfrom = obj.volInfo; -SPACEfrom = define_sampling_space(Vfrom, 1); - -obj_out = obj; -obj_out.dat = []; -obj_out.volInfo = Vto; - -obj = replace_empty(obj,'voxels'); % to make sure vox line up -% changed to 'voxels' only. SG 2/23/18 - -if ~isa(obj, 'atlas') - - % Standard image_vector objects - % ----------------------------------------------------------------------- - - for i = 1:n_imgs - - voldata = iimg_reconstruct_vols(obj.dat(:, i), obj.volInfo); - - resampled_dat = interp3(SPACEfrom.Xmm, SPACEfrom.Ymm, SPACEfrom.Zmm, voldata, SPACEto.Xmm, SPACEto.Ymm, SPACEto.Zmm, varargin{:}); - - resampled_dat = resampled_dat(:); - - obj_out.dat(:, i) = resampled_dat(Vto.wh_inmask); - - end - - % in case of NaN values - obj_out.dat(isnan(obj_out.dat)) = 0; - - - % Special object subtypes - % ----------------------------------------------------------------------- - -else % if isa(obj, 'atlas') - if ~isempty(obj.probability_maps) - n_prob_imgs = size(obj.probability_maps, 2); - - % voldata = iimg_reconstruct_vols(obj.probability_maps(:, 1), obj.volInfo); % commented out - Byeol, 01/15/2026 - obj_out.probability_maps = []; - - % resampled_dat = interp3(SPACEfrom.Xmm, SPACEfrom.Ymm, SPACEfrom.Zmm, voldata, SPACEto.Xmm, SPACEto.Ymm, SPACEto.Zmm, varargin{:}); - % resampled_dat = resampled_dat(:); - - % Use probability images if available - for i = 1:n_prob_imgs - - voldata = iimg_reconstruct_vols(obj.probability_maps(:, i), obj.volInfo); - - resampled_dat = interp3(SPACEfrom.Xmm, SPACEfrom.Ymm, SPACEfrom.Zmm, voldata, SPACEto.Xmm, SPACEto.Ymm, SPACEto.Zmm, varargin{:}); - - resampled_dat = resampled_dat(:); - - obj_out.probability_maps(:, i) = resampled_dat(Vto.wh_inmask); - - end - - obj_out.probability_maps(:, i) = resampled_dat(Vto.wh_inmask); - - % rebuild .dat from probability images - done below - % if n_prob_imgs - % obj_out = probability_maps_to_region_index(obj_out); - % end - - % if no prob images, need to be careful about how to resample integer vector data - - else - - % integer_vec = zeros(Vto.n_inmask, 1); - - % n_index_vals = length(unique(obj.dat(obj.dat ~= 0))); % I don't think drawing from the data field is appropriate, since it can be set in a variety of ways such that different regions get the same value? - n_index_vals = length(obj.labels); % Instead, simply count the number of label entries. - MS - - % create a set of pseudo-"probabilities" for each region, resampled. Then - % we can take the max prob, so that each voxel gets assigned to the best-matching parcel. - - pseudo_prob = zeros(Vto.n_inmask, n_index_vals); - - for i = 1:n_index_vals - - %myintegervec = i * double(obj.dat(:, 1) == i); - - myintegervec = double(obj.dat(:, 1) == i); % 1/0 "pseudo-probability" - - voldata = iimg_reconstruct_vols(myintegervec, obj.volInfo); - - resampled_dat = interp3(SPACEfrom.Xmm, SPACEfrom.Ymm, SPACEfrom.Zmm, voldata, SPACEto.Xmm, SPACEto.Ymm, SPACEto.Zmm, varargin{:}); - - resampled_dat = resampled_dat(:); - resampled_dat = resampled_dat(Vto.wh_inmask); % take relevant voxels only - % resampled_dat(~(round(resampled_dat) == i)) = 0; % take only values that round to integer - - pseudo_prob(:, i) = resampled_dat; - %integer_vec = integer_vec + round(resampled_dat); - - end - - obj_out.probability_maps = pseudo_prob; - - % obj_out.dat = integer_vec; % will be rounded later, but should be rounded already here... - - end % rebuild integers - - % rebuild .dat from probability images - obj_out = probability_maps_to_region_index(obj_out); - -end % atlas object - - -if isa(obj_out, 'statistic_image') - % Rebuild fields specific to statistic_images - -% obj_out = replace_empty(obj_out); % TOR REMOVED 5/18/21, AS IT -% RESULTS IN VOXEL LIST MISMATCH WITH PARTIALLY BUILT OBJECT FIELDS - - k = size(obj_out.dat, 2); - - [obj_out.p, obj_out.ste, obj_out.sig, obj_out.N] = deal([]); % these will be resampled - - p = ones(obj.volInfo.nvox, k); - ste = Inf .* ones(obj.volInfo.nvox, k); - sig = zeros(obj.volInfo.nvox, k); - N = zeros(obj.volInfo.nvox, 1); - - for i = 1:k - % this may break if nvox (total in image) is different for 2 - % images... - - % Wani: in some cases, obj could have empty p, ste, and sig - % Tor, 4/2021. Need to resample these appropriately, handle - % logicals, and add N field - - if ~isempty(obj.p) - - p(obj.volInfo.wh_inmask, i) = obj.p(:, i); - - voldata = iimg_reconstruct_vols(p(:, i), obj.volInfo); - resampled_dat = interp3(SPACEfrom.Xmm, SPACEfrom.Ymm, SPACEfrom.Zmm, voldata, SPACEto.Xmm, SPACEto.Ymm, SPACEto.Zmm, varargin{:}); - resampled_dat = resampled_dat(:); - obj_out.p(:, i) = resampled_dat(Vto.wh_inmask); - end - - if ~isempty(obj.ste) - - ste(obj.volInfo.wh_inmask, i) = obj.ste(:, i); - - voldata = iimg_reconstruct_vols(ste(:, i), obj.volInfo); - resampled_dat = interp3(SPACEfrom.Xmm, SPACEfrom.Ymm, SPACEfrom.Zmm, voldata, SPACEto.Xmm, SPACEto.Ymm, SPACEto.Zmm, varargin{:}); - resampled_dat = resampled_dat(:); - obj_out.ste(:, i) = resampled_dat(Vto.wh_inmask); - - end - - % For .sig, we must convert from logical and threshold back to logical - % Can't interpolate logical vectors - if ~isempty(obj.sig) - - % --------------------------------------------- - % Added by Zizhuang Miao 10/28/2024 - % the default linear interpolation during resampling - % could render .sig field in statistic images invalid, - % because it could make many voxels with an original .sig = 0 into - % having a .sig = 1 as long as it is close to a significant voxel - % generally we won't recommend resampling .sig field - % give a warning on that - warning(['Resampling voxel significance can cause false positives or negatives. ' ... - 'Consider resampling data before running statistical analysis.']); - % --------------------------------------------- - - sig(obj.volInfo.wh_inmask, i) = double(obj.sig(:, i)); - voldata = iimg_reconstruct_vols(sig(:, i), obj.volInfo); - - % ----------------------------------------- - % Edited by Zizhuang Miao 10/28/2024 - % nearest neighbor method can limit false positives, - % so use that as default unless otherwise specified - if nargin > 2 - if strcmp(varargin{:}, 'linear') - % if users specify using linear interpolation - warning(['Linear interpolation will lead to many false positives. ' ... - 'Consider using the default nearest neighbor method.']); - elseif ~strcmp(varargin{:}, 'nearest') - warning('Nearest neighbor is recommended.') - resampled_dat = interp3(SPACEfrom.Xmm, SPACEfrom.Ymm, SPACEfrom.Zmm, voldata, SPACEto.Xmm, SPACEto.Ymm, SPACEto.Zmm, varargin{:}); - end - else - % default to nearest neighbor - warning(['Using nearest neighbor method to resample .sig field. ' ... - 'This can limit false positives, but can also cause a mismatch ' ... - 'between .sig and other fields like .p.']) - resampled_dat = interp3(SPACEfrom.Xmm, SPACEfrom.Ymm, SPACEfrom.Zmm, voldata, SPACEto.Xmm, SPACEto.Ymm, SPACEto.Zmm, 'nearest'); - end - % ------------------------------------------ - - resampled_dat = resampled_dat(:); - resampled_dat(isnan(resampled_dat)) = 0; - obj_out.sig(:, i) = logical(resampled_dat(Vto.wh_inmask)); - end - - end - - if ~isempty(obj.N) - - N(obj.volInfo.wh_inmask, 1) = obj.N(:, 1); - - voldata = iimg_reconstruct_vols(N, obj.volInfo); - resampled_dat = interp3(SPACEfrom.Xmm, SPACEfrom.Ymm, SPACEfrom.Zmm, voldata, SPACEto.Xmm, SPACEto.Ymm, SPACEto.Zmm, varargin{:}); - resampled_dat = resampled_dat(:); - obj_out.N = resampled_dat(Vto.wh_inmask); - - end - -% if ~isempty(obj.p), obj_out.p = p(Vto.wh_inmask, :); end -% if ~isempty(obj.ste), obj_out.ste = ste(Vto.wh_inmask, :); end -% if ~isempty(obj.sig), obj_out.sig = sig(Vto.wh_inmask, :); end - -end - -% End special object subtypes -% ----------------------------------------------------------------------- - - -% Handle removed voxels -% ----------------------------------------------------------------------- - -if size(obj_out.dat, 1) == sum(obj_out.volInfo.image_indx) - % this should always/almost always be true - assign missing/removed vox - obj_out.removed_voxels = ~obj_out.volInfo.image_indx; - obj_out.removed_voxels = obj_out.removed_voxels(obj_out.volInfo.wh_inmask); - -else - obj_out.removed_voxels = false; -end - -% add clusters if needed -if obj_out.volInfo(1).n_inmask < 50000 - obj_out.volInfo(1).cluster = spm_clusters(obj_out.volInfo(1).xyzlist')'; -else - obj_out.volInfo(1).cluster = ones(obj_out.volInfo(1).n_inmask, 1); -end - -% No longer need to remove - tor 5/27/15 -% if isa(obj_out, 'statistic_image') && ~isempty(obj_out.sig) -% disp('resample_space: removing threshold information from statistic_image') -% obj_out.sig = []; -% end - -obj = obj_out; - - -if isempty(obj_out) - % return - nothing more to do - return -end - -% This stuff below added 1/27/13 by tor - -% re-parse clusters -obj = reparse_contiguous(obj, 'nonempty'); - -obj.history{end+1} = sprintf('Resampled data to space of %s', sampleto.volInfo.fname); - -% Special object subtypes -% ----------------------------------------------------------------------- - -if isa(obj, 'fmri_data') - % fmri_data has this field, but other image_vector objects do not. - obj.mask = resample_space(obj.mask, sampleto); -end - -% if isa(obj, 'statistic_image') -% % statistic_image has this field, but other image_vector objects do not. -% obj.sig = ones(size(obj.dat)); -% disp('.sig field reset. Re-threshold if necessary.'); -% end - -if isa(obj, 'atlas') - % Rebuild index so we have integers only. Rebuild if we have prob maps, - % or round .dat if not. - n_regions = max([size(obj.probability_maps, 2) length(obj.labels)]); % edit from num_regions method to exclude using .dat, as we are trying to adjust dat for interpolation - - has_pmaps = ~isempty(obj.probability_maps) && size(obj.probability_maps, 2) == n_regions; - - if has_pmaps - obj = probability_maps_to_region_index(obj); - else - obj.dat = int32(round(obj.dat)); - end - - [obj, ~, ~, missing_regions] = check_properties(obj, 'compress_index'); % check. adjust indices and print warning if we have lost regions - - if any(missing_regions) - disp('Some atlas regions lost in resampling:'); - disp(missing_regions'); - end - -end - -% End special object subtypes -% ----------------------------------------------------------------------- - - - -end - -% % -% % % --------------------------------------------------------- -% % % Define image name to sample to and volInfo from input -% % % --------------------------------------------------------- -% % % Sampleto may be one of these: -% % % 1) a volInfo structure (the image does not have to exist on the path) -% % % 2) an image_vector, fmri_data, fmri_mask_image object -% % % 3) a string with the name of an image -% % -% % [image_name_to_sample_to, volInfo_to, imgobj_to] = define_sample_to_image(sampleto); -% % -% % fprintf('Resampling data from %3.0f images to space of %s\n', n_imgs, image_name_to_sample_to); -% % -% % % --------------------------------------------------------- -% % % If the images corresponding to data we're resampling don't exist, -% % % we must write them to disk -% % % --------------------------------------------------------- -% % obj = check_image_filenames(obj); -% % -% % if ~all(obj.files_exist) -% % disp('Writing temporary image file for resampling. This will be removed.') -% % fullpath_orig = obj.fullpath; % save the name for later -% % -% % str = 'qwertyuiopasdfghjkl'; -% % str = str(randperm(length(str))); -% % name = sprintf('tmp_image%s.img', str); -% % obj.fullpath = fullfile(pwd, name); -% % write(obj); -% % end -% % -% % newdat = zeros(volInfo_to.n_inmask, n_imgs, 'single'); -% % -% % for i = 1:n_imgs -% % -% % if size(obj.fullpath, 1) < i -% % error('%3.0f images in obj.dat, but only %3.0f image names in obj.fullpath.', n_imgs, size(obj.fullpath, 1)); -% % end -% % -% % % resample to new space and re-extract vector -% % loadImg = deblank(obj.fullpath(i, :)); % ',' num2str(i)]); %**may be issues with 4-D vs. 3-D files -% % -% % imgData = scn_map_image(loadImg, volInfo_to); -% % newdat(:, i) = imgData(volInfo_to.wh_inmask); -% % -% % end -% % -% % obj.dat = newdat; -% % obj.volInfo = volInfo_to; -% % -% % if isa(obj, 'fmri_data') -% % % fmri_data has this field, but other image_vector objects do not. -% % obj.mask = resample_to_image_space(obj.mask, imgobj_to); -% % end -% % -% % if isa(obj, 'statistic_image') -% % % statistic_image has this field, but other image_vector objects do not. -% % obj.sig = ones(size(obj.dat)); -% % disp('.sig field reset. Re-threshold if necessary.'); -% % end -% % -% % obj.history{end+1} = sprintf('Resampled data to space of %s', image_name_to_sample_to); -% % -% % % Clean up: remove temporary images -% % if ~all(obj.files_exist) -% % str = sprintf('!rm %s*', obj.fullpath(1:end-4)); -% % disp('Removing temporary files:') -% % disp(str) -% % eval(str) -% % obj.fullpath = fullpath_orig; -% % end -% % -% % % removed voxels must be updated due to resampling -% % obj.removed_voxels = false(obj.volInfo.n_inmask, 1); -% % -% % end % function -% % -% % % -% % % -% % % -% % % -% % % -% % -% % -% % function [image_name_to_sample_to, volInfo_to, imgobj_to] = define_sample_to_image(sampleto) -% % -% % switch class(sampleto) -% % case 'char' -% % image_name_to_sample_to = sampleto; -% % -% % volInfo_to = iimg_read_img(sampleto, 2, 1, 1); % read data from file, first volume only -% % -% % imgobj_to = fmri_mask_image(image_name_to_sample_to); -% % -% % case {'fmri_mask_image'} -% % -% % if ~isempty(sampleto.space_defining_image_name) -% % image_name_to_sample_to = sampleto.space_defining_image_name; -% % else -% % image_name_to_sample_to = sampleto.volInfo.fname; -% % end -% % -% % volInfo_to = sampleto.volInfo; -% % imgobj_to = sampleto; -% % -% % case {'image_vector', 'fmri_data', 'statistic_image'} -% % image_name_to_sample_to = sampleto.volInfo.fname; -% % volInfo_to = sampleto.volInfo; -% % imgobj_to = sampleto; -% % -% % case 'struct' -% % % assume its a volInfo structure -% % image_name_to_sample_to = sampleto.fname; -% % volInfo_to = sampleto; -% % -% % if exist(image_name_to_sample_to, 'file') -% % imgobj_to = fmri_mask_image(image_name_to_sample_to); -% % else -% % fprintf('Cannot resample to volInfo input struct because volInfo.fname image\n'); -% % fprintf('%s\n cannot be found.\n', image_name_to_sample_to); -% % end -% % -% % otherwise -% % disp('fmri_mask_image.resample_to_image_space: illegal sampleto input.'); -% % disp('Must be volInfo structure or image_vector, fmri_data, fmri_mask_image object') -% % disp('or a char name of an image.'); -% % error('exiting.'); -% % end -% % -% % end -% % -% % diff --git a/CanlabCore/@image_vector/resample_time.m b/CanlabCore/@image_vector/resample_time.m index d7f9554e..5c4de01b 100644 --- a/CanlabCore/@image_vector/resample_time.m +++ b/CanlabCore/@image_vector/resample_time.m @@ -1,8 +1,26 @@ function obj = resample_time(obj, source_TR, target_TR, varargin) -% Resample the time-series images (source_time_interval) in an fmri_data object (obj) -% to the different time series (target_time_interval). Works for all image_vector objects. +% resample_time Resample the time-series images of an image_vector object to a new TR. % -% - obj = resample_time(obj, source_time_interval, target_time_interval, varargin) +% Resample the time-series images (source_time_interval) in an fmri_data +% object (obj) to a different time series (target_time_interval). Works +% for all image_vector objects. +% +% :Usage: +% :: +% +% obj = resample_time(obj, source_TR, target_TR, [optional inputs]) +% +% :Inputs: +% +% **obj:** +% An image_vector or fmri_data object. Each column of obj.dat is +% a time point. +% +% **source_TR:** +% Source time interval (seconds), i.e. the original TR. +% +% **target_TR:** +% Target time interval (seconds) to resample to. % % :Optional Inputs: % @@ -13,24 +31,34 @@ % - 'spline' - spline interpolation % - 'cubic' - cubic interpolation as long as the data is uniformly % spaced, otherwise the same as 'spline' +% % **slice:** -% A fraction of the slice timing correction. -% The default is 0.5, meaning if your TR is 2s, the time point of your TR image -% will be considered as the middle point of the TR bins. You can use this option -% to use different time points. If you are upsampling your data (i.e., -% your target TR is shorter than your source TR), you need to discard the -% first column of your data. This function will return the first time point data as NaN. +% A fraction of the slice timing correction. +% The default is 0.5, meaning if your TR is 2s, the time point of your TR image +% will be considered as the middle point of the TR bins. You can use this option +% to use different time points. If you are upsampling your data (i.e., +% your target TR is shorter than your source TR), you need to discard the +% first column of your data. This function will return the first time point data as NaN. % +% :Outputs: +% +% **obj:** +% The input object with .dat resampled to the target TR using +% interp2 along the time dimension. % % :Examples: % :: % % dat = fmri_data('/Volumes/RAID1/labdata/current/BMRK3/Imaging/spatiotemp_biomarker/STmarker1.img'); -% dat = resample_time(dat, 2, 1.3) +% dat = resample_time(dat, 2, 1.3) % % % with options: % dat = resample_time(dat, 2, 1.3, 'meth', 'linear', 'slice', .3) % +% :See also: +% - resample_space +% - interp2 +% slice_frac = .5; intp_meth = []; diff --git a/CanlabCore/@image_vector/rmssd_movie.m b/CanlabCore/@image_vector/rmssd_movie.m index 7f82a5db..69dab1e5 100644 --- a/CanlabCore/@image_vector/rmssd_movie.m +++ b/CanlabCore/@image_vector/rmssd_movie.m @@ -1,51 +1,95 @@ function [rmssd, rmssd_outlier_regressor_matrix] = rmssd_movie(dat, varargin) -% Movie of successive differences (sagittal slice) -% Enter an image_vector or fmri_data object (usually with time series) +% rmssd_movie Movie of successive differences (sagittal slice) for an image_vector / fmri_data object. +% +% Enter an image_vector or fmri_data object (usually with time series). +% +% Images usually change slowly over time, and sudden changes in intensity +% can also often be a sign of bad things -- head movement artifact or +% gradient misfires, interacting with the magnetic field to create +% distortion across the brain. +% +% RMSSD tracks large changes across successive images, regardless of +% what the sign of the changes is or where they are. In addition, images +% with unusually high spatial standard deviation across voxels may be +% outliers with image intensity distortions in some areas of the image +% but not others (e.g., bottom half of brain vs. top half, or odd vs. +% even slices). +% +% The CANlab method rmssd_movie( ), for fmri_data objects, creates a +% visual movie so you can see what the image-to-images changes are. It +% pauses where they're unusual. It also returns a matrix +% rmssd_outlier_regressor_matrix, which has an indicator regressor (1 +% or 0 values) for every image that is quite different from the +% preceding ones (the pause point in the movie). This is based on two +% things: (1) rmssd being > a cutoff number of standard deviations from +% the mean, (2) spatial standard deviation of the images being > a +% cutoff number of standard deviations from the mean. The cutoff is 3 +% s.d. by default. This matrix can be added to your design matrix as a +% set of nuisance covariates of no interest. % % :Usage: % :: % -% [rmssd, rmssd_outlier_regressor_matrix] = rmssd_movie(dat, [full_path_of_movie_output_file,image_skip_interval]) -% -% Images usually change slowly over time, and sudden changes in intensity can also often be a sign of bad things -% -- head movement artifact or gradient misfires, interacting with the magnetic field to create distortion -% across the brain. -% RMSSD tracks large changes across successive images, regardless of what the sign of the changes is or where they are. -% In addition, images with unusually high spatial standard deviation across voxels may be outliers with image -% intensity distortions in some areas of the image but not others (e.g., bottom half of brain vs. top half, -% or odd vs. even slices). -% The CANlab method rmssd_movie( ), for fmri_data objects, creates a visual movie so you can see what the -% image-to-images changes are. It pauses where they're unusual. It also returns a matrix rmssd_outlier_regressor_matrix, -% which has an indicator regressor (1 or 0 values) for every image that is quite different from the preceding ones -% (the pause point in the movie). This is based on two things: (1) rmssd being > a cutoff number of standard -% deviations from the mean, (2) spatial standard deviation of the images being > a cutoff number of -% standard deviations from the mean. The cutoff is 3 s.d. by default. -% This matrix can be added to your design matrix as a set of nuisance covariates of no interest. -% -% *Optional Inputs: +% [rmssd, rmssd_outlier_regressor_matrix] = rmssd_movie(dat, [optional inputs]) +% +% :Inputs: +% +% **dat:** +% An image_vector / fmri_data object, usually with a time-series +% of images stored as columns of .dat. +% +% :Optional Inputs: % % **'movieoutfile', :** % Followed by a char array detailing the full path to save the -% movie file +% movie file. % % **'image_interval', n:** -% Followed by an integer value describing the interval -% between images in each subsequent frame of the movie -% (default = 1). Higher values will skip, showing every n images +% Followed by an integer value describing the interval between +% images in each subsequent frame of the movie (default = 1). +% Higher values will skip, showing every n images. +% +% **'sdlim', n:** +% Cutoff in standard deviations for flagging outliers. Default = 3. +% +% **'writetofile':** +% Force writing to disk; if 'movieoutfile' is empty, a default +% path in the current directory is used. +% +% **'nomovie' / 'nodisplay':** +% Suppress display of the movie figure (only return the values). +% +% **'showmovie', logical:** +% Explicitly toggle movie display. +% +% :Outputs: +% +% **rmssd:** +% Vector of root-mean-square successive differences across +% images, length = number of images in dat. +% +% **rmssd_outlier_regressor_matrix:** +% Matrix of nuisance regressors (one column per flagged image) +% indicating likely outlier images. % % :Examples: % :: % -% Show a movie of RMSSD and write a movie file to disk in the qc_images subdirectory: +% % Show a movie of RMSSD and write a movie file to disk in the qc_images subdirectory: % rmssd_movie(fmri_dat, 'movie_output_file', '/Subj1/qc_images/rmssd_movie', 'image_interval', 5) % -% This would save an movie based on the images in fmri_dat to the -% above directory, with an interval of 5 images between each -% frame (so, the movie would show image 1, 6, 11, 16, etc) +% % This would save an movie based on the images in fmri_dat to the +% % above directory, with an interval of 5 images between each +% % frame (so, the movie would show image 1, 6, 11, 16, etc) +% +% :See also: +% - slice_movie +% - preprocess (with 'outliers_rmssd') +% - intercept_model % - -% Programmers Notes: % .. +% Programmers Notes: +% % Edited 8/7/14 by Scott % - added skip interval % - updated help diff --git a/CanlabCore/@image_vector/slice_movie.m b/CanlabCore/@image_vector/slice_movie.m index bc1b50d3..c8907293 100644 --- a/CanlabCore/@image_vector/slice_movie.m +++ b/CanlabCore/@image_vector/slice_movie.m @@ -1,46 +1,82 @@ function outlier_tables = slice_movie(dat, varargin) -% Movie of slice timeseries (sagittal slice) -% Enter an image_vector or fmri_data object (usually with time series) +% slice_movie Movie of slice timeseries (sagittal + axial slices) for an image_vector / fmri_data object. +% +% Enter an image_vector or fmri_data object (usually with time series). +% +% Images usually change slowly over time, and sudden changes in intensity +% can also often be a sign of bad things -- head movement artifact or +% gradient misfires, interacting with the magnetic field to create +% distortion across the brain. +% +% RMSSD tracks large changes across successive images, regardless of +% what the sign of the changes is or where they are. In addition, images +% with unusually high spatial standard deviation across voxels may be +% outliers with image intensity distortions in some areas of the image +% but not others (e.g., bottom half of brain vs. top half, or odd vs. +% even slices). +% +% The CANlab method slice_movie( ), for fmri_data objects, creates a +% visual movie so you can see what the image-to-images changes are. It +% pauses where they're unusual, as defined by the method +% fmri_data.outliers( ). It also returns a table, outlier table, and +% matrices you can use as covariates in design +% outlier_regressor_matrix_uncorr and outlier_regressor_matrix_corr. +% These have an indicator regressor (1 or 0 values) for every image +% that is quite different from the preceding ones (the pause point in +% the movie). This is based on two things: (1) rmssd and others being > +% a cutoff number of standard deviations from the mean, (2) spatial +% standard deviation of the images being > a cutoff number of standard +% deviations from the mean. The cutoff is 3 median absolute deviations +% by default. This matrix can be added to your design matrix as a set +% of nuisance covariates of no interest. % % :Usage: % :: % -% [outlier_table, outlier_regressor_matrix_uncorr, outlier_regressor_matrix_corr] = slice_movie(dat, [full_path_of_movie_output_file,image_skip_interval]) +% outlier_tables = slice_movie(dat, [optional inputs]) % -% Images usually change slowly over time, and sudden changes in intensity can also often be a sign of bad things -% -- head movement artifact or gradient misfires, interacting with the magnetic field to create distortion -% across the brain. -% RMSSD tracks large changes across successive images, regardless of what the sign of the changes is or where they are. -% In addition, images with unusually high spatial standard deviation across voxels may be outliers with image -% intensity distortions in some areas of the image but not others (e.g., bottom half of brain vs. top half, -% or odd vs. even slices). -% The CANlab method slice_movie( ), for fmri_data objects, creates a visual movie so you can see what the -% image-to-images changes are. It pauses where they're unusual, as defined by the method fmri_data.outliers( ). -% It also returns a table, outlier table, and matrices you can use as covariates in design -% outlier_regressor_matrix_uncorr and outlier_regressor_matrix_corr. -% These have an indicator regressor (1 or 0 values) for every image that is quite different from the preceding ones -% (the pause point in the movie). This is based on two things: (1) rmssd and others being > a cutoff number of standard -% deviations from the mean, (2) spatial standard deviation of the images being > a cutoff number of -% standard deviations from the mean. The cutoff is 3 median absolute deviations. by default. -% This matrix can be added to your design matrix as a set of nuisance covariates of no interest. +% :Inputs: % -% *Optional Inputs: +% **dat:** +% An image_vector / fmri_data object, usually with a time-series +% of images stored as columns of .dat. +% +% :Optional Inputs: % % **'movieoutfile', :** % Followed by a char array detailing the full path to save the -% movie file +% movie file % % **'image_interval', n:** -% Followed by an integer value describing the interval -% between images in each subsequent frame of the movie -% (default = 1). Higher values will skip, showing every n images +% Followed by an integer value describing the interval between +% images in each subsequent frame of the movie (default = 1). +% Higher values will skip, showing every n images % % **'montage':** -% Show montage of all slices, rather than just 2 slices +% Show montage of all slices, rather than just 2 slices. % % **'nooutliers':** % Skip the outlier detection stage (can speed things up with long -% image series) +% image series). +% +% **'madlim', n:** +% Median absolute deviation cutoff for flagging outliers. Default = 3. +% +% **'writetofile':** +% Force writing to disk; if 'movieoutfile' is empty, a default +% path in the current directory is used. +% +% **'nomovie' / 'nodisplay':** +% Suppress display of the movie figure. +% +% **'dostepthrough':** +% Step through images one at a time, waiting for keypress. +% +% :Outputs: +% +% **outlier_tables:** +% A struct of outlier tables (e.g., score_table) returned by +% outliers(). % % :Examples: % :: @@ -49,18 +85,21 @@ % obj2 = rescale(obj, 'l2norm_images'); % slice_movie(obj2); % -% Show a movie of RMSSD and write a movie file to disk in the qc_images subdirectory: +% % Show a movie of RMSSD and write a movie file to disk in the qc_images subdirectory: % rmssd_movie(fmri_dat, 'movie_output_file', '/Subj1/qc_images/rmssd_movie', 'image_interval', 5) % -% This would save an movie based on the images in fmri_dat to the -% above directory, with an interval of 5 images between each -% frame (so, the movie would show image 1, 6, 11, 16, etc) +% % This would save an movie based on the images in fmri_dat to the +% % above directory, with an interval of 5 images between each +% % frame (so, the movie would show image 1, 6, 11, 16, etc) % - -% Programmers Notes: -% .. +% :See also: +% - rmssd_movie +% - outliers +% - display_slices % % .. +% Programmers Notes: +% .. % ------------------------------------------------------------------------- % DEFAULT ARGUMENT VALUES diff --git a/CanlabCore/@image_vector/slices.m b/CanlabCore/@image_vector/slices.m index b62b3de1..35cc05dd 100644 --- a/CanlabCore/@image_vector/slices.m +++ b/CanlabCore/@image_vector/slices.m @@ -1,14 +1,23 @@ function o = slices(obj, varargin) -% Create a montage of single-slice results for every image in an image_vector object +% slices Create a montage of single-slice results for every image in an image_vector object. +% +% obj is an image_vector, fmri_data, or statistic_image object with +% multiple images (only the first 64 will display), which are stored as +% columns in its .dat field. +% +% This function uses fmridisplay objects, and may be memory-intensive +% for older computers. % % :Usage: % :: % % o = slices(obj, 'orientation', [orientation], 'slice', [slice_mm], 'nimages', [nimgs]) % -% obj is an image_vector, fmri_data, or statistic_image object with -% multiple images (only the first 64 will display), which are stored as -% columns in its .dat field. +% :Inputs: +% +% **obj:** +% An image_vector / fmri_data / statistic_image object whose +% columns of .dat are the images to display. % % :Optional Inputs: % @@ -31,16 +40,16 @@ % **outline:** % is followed by a color vector for outline around blobs. % -% The output, o, is an fmridisplay object. +% :Outputs: % -% This function uses fmridisplay objects, and may be memory-intensive for -% older computers. +% **o:** +% An fmridisplay object holding the composite montage. % -% *Common Errors:* +% Common Errors: % -% This function uses the volInfo.cluster field. If you create a mask in an -% ad hoc way, this field may not be updated. use this to fix: -% - mask = reparse_contiguous(mask); +% This function uses the volInfo.cluster field. If you create a mask in +% an ad hoc way, this field may not be updated. Use this to fix: +% mask = reparse_contiguous(mask); % % :Examples: % :: @@ -52,6 +61,12 @@ % % o2 = slices(all_chi2_images, 'orientation', 'saggital', 'slice', 0); % +% :See also: +% - fmridisplay +% - montage +% - region +% - reparse_contiguous +% % .. % Copyright 2011, Tor Wager % .. diff --git a/CanlabCore/@image_vector/surface.m b/CanlabCore/@image_vector/surface.m index 472d27fe..6aaa431e 100644 --- a/CanlabCore/@image_vector/surface.m +++ b/CanlabCore/@image_vector/surface.m @@ -1,18 +1,63 @@ function [all_surf_handles, pcl, ncl] = surface(obj, varargin) -% Render image data on brain surfaces; options for cutaways and canonical surfaces +% surface Render image_vector data on brain surfaces; cutaways and canonical surfaces. % -% *Usage:* -% [all_surf_handles, pcl, ncl] = surface(obj) -% [all_surf_handles, pcl, ncl] = surface(r, ['cutaways', any optional inputs to surface_cutaway]) -% % This function uses region.surface to create surface figures. % See help region.surface for options. % +% :Usage: +% :: +% +% [all_surf_handles, pcl, ncl] = surface(obj) +% [all_surf_handles, pcl, ncl] = surface(obj, 'cutaways', [optional inputs to surface_cutaway]) +% +% :Inputs: +% +% **obj:** +% An image_vector, fmri_data, statistic_image, or atlas object. +% If multiple images are present, the mean is plotted. +% +% :Optional Inputs: +% +% All optional inputs are passed through to region.surface and on to +% surface_cutaway / mediation_brain_surface_figs. Common options: +% +% **'cutaway':** +% Render with cutaway surfaces (uses surface_cutaway). +% +% **'foursurfaces':** +% Render four canonical surface views. +% +% **'pos_colormap', cm / 'neg_colormap', cm:** +% Colormaps for positive / negative values. +% +% **'ycut_mm', value:** +% Y position (mm) of cutaway plane. +% +% **'existingfig':** +% Use the current figure rather than creating a new one. +% +% **'surface_handles', handles:** +% Plot onto an existing set of surface handles. +% +% **'color_upperboundpercentile', pct / 'color_lowerboundpercentile', pct:** +% Percentile bounds for color scaling. +% +% :Outputs: +% +% **all_surf_handles:** +% Handles to surface objects created. +% +% **pcl:** +% Region object for positive-valued clusters. +% +% **ncl:** +% Region object for negative-valued clusters. +% % :Examples: % :: % % % Create an initial surface plot from an fmri_data object: -% han = surface(regionmasks{2}); +% han = surface(regionmasks{2}); % % % Now add a second region in green: % cluster_surf(region(regionmasks{2}), {[0 1 0]}, han, 5); @@ -27,9 +72,17 @@ % all_surf_handles = mediation_brain_surface_figs([]); % surface(t2, 'cutaway', 'surface_handles', all_surf_handles, 'color_upperboundpercentile', 95, 'color_lowerboundpercentile', 5, 'neg_colormap', colormap_tor([0 0 1], [.2 0 .5])); % - -% :Programmers' notes: -% 9-8-2020 fixed weird bug that broke this somehow. Maybe someone deleted a line of code and committed? +% :See also: +% - region.surface +% - surface_cutaway +% - mediation_brain_surface_figs +% - render_on_surface +% - montage +% +% .. +% Programmers' notes: +% 9-8-2020 fixed weird bug that broke this somehow. Maybe someone deleted a line of code and committed? +% .. if size(obj.dat, 2) > 1 obj = mean(obj); diff --git a/CanlabCore/@image_vector/trim_mask.m b/CanlabCore/@image_vector/trim_mask.m index 3ec03ba6..e88d6994 100644 --- a/CanlabCore/@image_vector/trim_mask.m +++ b/CanlabCore/@image_vector/trim_mask.m @@ -1,5 +1,36 @@ function obj = trim_mask(obj) -% Exclude empty voxels from mask information in obj.volInfo structure, and re-make obj.volInfo +% trim_mask Exclude empty voxels from the mask in obj.volInfo and rebuild .volInfo. +% +% Removes voxels whose values across all images are zero or NaN, and +% updates the .volInfo bookkeeping (image_indx, wh_inmask, xyzlist, +% cluster, n_inmask) accordingly. For fmri_data objects, the embedded +% .mask is updated in parallel. +% +% :Usage: +% :: +% +% obj = trim_mask(obj) +% +% :Inputs: +% +% **obj:** +% An image_vector / fmri_data object. +% +% :Outputs: +% +% **obj:** +% The input with all-zero / all-NaN voxels removed from .dat and +% from .volInfo (and from .mask for fmri_data objects). +% +% :Examples: +% :: +% +% obj = trim_mask(obj); +% +% :See also: +% - remove_empty +% - replace_empty +% - rebuild_volinfo_from_dat % % .. % Tor Wager, 2013 diff --git a/CanlabCore/@image_vector/union.m b/CanlabCore/@image_vector/union.m index 5e739e97..255c778d 100644 --- a/CanlabCore/@image_vector/union.m +++ b/CanlabCore/@image_vector/union.m @@ -1,14 +1,50 @@ function [dat, dati] = union(dat1, dat2, outputname) -% Union and intersection masks for two image_vector objects +% union Compute union and intersection masks for two image_vector objects. +% +% Reconstructs both inputs into full image volumes, then computes a new +% image_vector object whose voxels are the OR (union) of the two input +% masks. A second output object holds the AND (intersection). For +% statistic_image inputs, .p / .ste / .sig fields are combined +% accordingly. Both inputs must currently be in the same image space. % % :Usage: % :: % % [dat_union, dat_intersection] = union(dat1, dat2, outputname) % -% dat = union(dat1, dat2, outputname) -% outputname = character array name for union image -% INCLUDE .img at the end. +% :Inputs: +% +% **dat1:** +% First image_vector / fmri_data / statistic_image object. +% +% **dat2:** +% Second image_vector / fmri_data / statistic_image object. +% Must be in the same space as dat1 (compare_space must return +% 0 or 3). +% +% **outputname:** +% Optional. Character array filename for the union image +% (INCLUDE .img at the end). If supplied, the union object is +% written to disk in the current directory. +% +% :Outputs: +% +% **dat:** +% Union image_vector object (voxels in dat1 OR dat2). +% +% **dati:** +% Intersection image_vector object (voxels in dat1 AND dat2). +% +% :Examples: +% :: +% +% [u, i] = union(mask1, mask2); +% [u, i] = union(mask1, mask2, 'union_mask.img'); +% +% :See also: +% - compare_space +% - reconstruct_image +% - apply_mask % % .. % NOTE: must now be in same space! diff --git a/CanlabCore/@image_vector/unstack_by_condition.m b/CanlabCore/@image_vector/unstack_by_condition.m index 73cb3a34..b97041e2 100644 --- a/CanlabCore/@image_vector/unstack_by_condition.m +++ b/CanlabCore/@image_vector/unstack_by_condition.m @@ -1,33 +1,78 @@ function [obj_separated_matched, levels_table] = unstack_by_condition(obj, condition_names, sid_var_name) -% unstack image into separate objects for each condition based on 1 or more metadata_table variables +% unstack_by_condition Split an image_vector into per-condition objects matched on a subject variable. % -% [obj_separated_matched, levels_table] = unstack_by_condition(obj, condition_names, sid_var_name) +% Unstack image into separate objects for each condition based on one or +% more metadata_table variables. % -% This object method uses the metadata_table field of an image_vector object (e.g., fmri_data object) -% to separate images belonging to different groups or conditions. It can be used along with the mean() -% method to create average images for subgroups (e.g., participants) separated by condition. This turns -% a "long format" object, for example one with multiple trials per condition per participant, into a -% "wide format" dataset with one object per condition, with the order of images matched on participant -% across conditions. This "wide format" is suitable for estimating within-person contrasts and doing -% related statistical tests. +% This object method uses the metadata_table field of an image_vector +% object (e.g., fmri_data object) to separate images belonging to +% different groups or conditions. It can be used along with the mean() +% method to create average images for subgroups (e.g., participants) +% separated by condition. This turns a "long format" object, for example +% one with multiple trials per condition per participant, into a "wide +% format" dataset with one object per condition, with the order of +% images matched on participant across conditions. This "wide format" is +% suitable for estimating within-person contrasts and doing related +% statistical tests. % -% e.g., -% condition_names = {'stimLvl' 'heat' 'reg'}; % condition names -% sid_var_name = 'subject_id'; % must be numeric +% This will create a cell array with one cell per unique combination of +% the levels of variables in condition_names. E.g., if there are 3 +% levels of reg, 2 levels of stimLvl, and 1 level of heat, this will +% create 3 x 2 = 6 separate image objects, each in a cell. % -% This will create a cell array with one cell per unique combination of the -% levels of variables in condition_names -% e.g., if there are 3 levels of reg, 2 levels of stimLvl, and 1 level of heat -% will create 3 x 2 = 6 separate image objects, each in a cell +% To handle missing data, assuming conditions are within-subject, match +% images in each object on a subject (or other grouping) variable. % -% To handle missing data, assuming conditions are within-subject: -% Match images in each object on a subject (or other grouping) variable % * All variables must be numeric * +% % This is an alpha-version code stub; please finish me using % documentation_template.m - -% Programmers' notes: -% Created by Tor Wager, Jan 2024 +% +% :Usage: +% :: +% +% [obj_separated_matched, levels_table] = unstack_by_condition(obj, condition_names, sid_var_name) +% +% :Inputs: +% +% **obj:** +% An image_vector / fmri_data object with a populated +% metadata_table. +% +% **condition_names:** +% Cell array of column names in obj.metadata_table that define +% the conditions to unstack on. +% +% **sid_var_name:** +% Name of a numeric subject (or grouping) variable in +% obj.metadata_table used to match images across conditions. +% +% :Outputs: +% +% **obj_separated_matched:** +% Cell array of image_vector objects, one per unique combination +% of levels in condition_names, with rows matched across cells +% on sid_var_name. +% +% **levels_table:** +% Table of unique condition combinations corresponding to the +% cells of obj_separated_matched. +% +% :Examples: +% :: +% +% condition_names = {'stimLvl' 'heat' 'reg'}; +% sid_var_name = 'subject_id'; % must be numeric +% [obj_sep, levels] = unstack_by_condition(obj, condition_names, sid_var_name); +% +% :See also: +% - get_wh_image +% - mean +% +% .. +% Programmers' notes: +% Created by Tor Wager, Jan 2024 +% .. % matched rows on subject diff --git a/CanlabCore/@image_vector/wedge_plot_by_atlas.m b/CanlabCore/@image_vector/wedge_plot_by_atlas.m index 06c08f59..d418d1fc 100644 --- a/CanlabCore/@image_vector/wedge_plot_by_atlas.m +++ b/CanlabCore/@image_vector/wedge_plot_by_atlas.m @@ -1,87 +1,127 @@ function [hh, output_values_by_region, labels, atlas_obj, colorband_colors, atlastab] = wedge_plot_by_atlas(obj_to_plot, varargin) -% Plot a data object or 'signature' pattern divided into local regions -% based on atlas objects. +% wedge_plot_by_atlas Plot a data object or 'signature' pattern divided into local regions based on atlas objects. % -% [hh, output_values_by_region, labels] = wedge_plot_by_atlas(obj, varargin) +% Renders one wedge plot per atlas, where each wedge corresponds to a +% region in the atlas. In 'data mode' (default), wedge size is the +% absolute value of the parcel mean and color encodes the sign. In +% 'signature mode', wedge size is normalized local pattern expression +% and color encodes pattern valence (cosine similarity to a unit vector). % +% :Usage: +% :: % -% 'signature' : signature mode; changes the statistic that is plotted -% 'atlases' : followed by atlas names as in a cell array defined in load_atlas.m -% 'colors' : followed by 2-element cell with colors for negative and positive values, respectively -% 'montage' : create montage figure for each atlas -% 'colorband_colors' : followed by cell array (length = number of atlases) with arrays of colors for each region +% [hh, output_values_by_region, labels, atlas_obj, colorband_colors, atlastab] = wedge_plot_by_atlas(obj_to_plot, [optional inputs]) % -% 'Signature mode': -% Size of wedges: Pattern energy, related to root mean squared weights in each region -% Normalized local pattern expression (sqrt(w''*w)/(vol_in_mm+1000) +% :Inputs: % -% Color of wedges: Proportional to valence, cosine similarity to unit -% vector. A measure of whether weights are uniformly positive (red), -% uniformly negative (blue) or mixed (purple). +% **obj_to_plot:** +% An image_vector / fmri_data / statistic_image object containing +% the data or signature pattern to plot. % +% :Optional Inputs: % -% 'Data mode': (default) -% Size of wedges: absolute value of the pattern mean within a parcel -% Color of wedges: sign of the pattern mean within a parcel +% **'signature':** +% signature mode; changes the statistic that is plotted % -% Outputs: -% ------------------------------------------------------------------------ -% output_values_by_region: Cell array, one cell per atlas, with a matrix of -% images x regions in atlas. Values depend on whether you use 'signature -% mode' or 'data mode'. +% **'atlases':** +% followed by atlas names as in a cell array defined in load_atlas.m % -% labels: region names for each atlas +% **'colors':** +% followed by 2-element cell with colors for negative and positive values, respectively % -% atlas_obj: cell array of atlas objects +% **'montage':** +% create montage figure for each atlas % -% colorband_colors: cell array of colors for each atlas +% **'colorband_colors':** +% followed by cell array (length = number of atlases) with arrays of colors for each region % -% atlastab: Table object with regions and values +% **'surfaces':** +% Render the regions on cortical surfaces with a legend. % -% Examples: -% % ------------------------------------------------------------------------ -% Load sig -% nps = load_image_set('npsplus'); -% nps = get_wh_image(nps, 1); -% sig_to_plot = nps; +% 'Signature mode': +% Size of wedges: Pattern energy, related to root mean squared weights in each region +% Normalized local pattern expression (sqrt(w''*w)/(vol_in_mm+1000) % -% Plot: -% hh = wedge_plot_by_atlas(sig_to_plot, 'signature') -% hh = wedge_plot_by_atlas(sig_to_plot,'signature','colors',{[0 0 1],[1 0 0]},'montage') +% Color of wedges: Proportional to valence, cosine similarity to unit +% vector. A measure of whether weights are uniformly positive (red), +% uniformly negative (blue) or mixed (purple). % +% 'Data mode' (default): +% Size of wedges: absolute value of the pattern mean within a parcel +% Color of wedges: sign of the pattern mean within a parcel % -% Try with VPS: -% [sigs, signames] = load_image_set('npsplus'); -% sig_to_plot = get_wh_image(sigs, 7); signames{7} -% hh = wedge_plot_by_atlas(sig_to_plot, 'signature') -% hh = wedge_plot_by_atlas(sig_to_plot,'signature','colors',{[0 0 1],[1 0 0]},'montage') +% :Outputs: % -% Try some data: -% imgs = load_image_set('emotionreg'); -% hh = wedge_plot_by_atlas(imgs, 'atlases', {'cit168' 'brainstem'}); +% **hh:** +% Cell array of wedge-plot handles, one cell per atlas. % -% Try custom colors, mirroring clusters across L/R hem networks: -% [colors1, colors2] = deal(scn_standard_colors(16)); -% colors = {}; -% indx = 1; -% for i = 1:length(colors1) -% colors{indx} = colors1{i}; colors{indx + 1} = colors2{i}; indx = indx + 2; -% end -% [hh, output_values_by_region, labels, atlas_obj, colorband_colors] = wedge_plot_by_atlas(imgs, 'atlases', {'yeo17networks'}, 'montage', 'colorband_colors', colors); - -% Notes on pattern valence -% ------------------------------------------------------------------ -% built into apply_parcellation +% **output_values_by_region:** +% Cell array, one cell per atlas, with a matrix of images x +% regions in atlas. Values depend on whether you use 'signature +% mode' or 'data mode'. +% +% **labels:** +% region names for each atlas +% +% **atlas_obj:** +% cell array of atlas objects +% +% **colorband_colors:** +% cell array of colors for each atlas +% +% **atlastab:** +% Table object with regions and values +% +% :Examples: +% :: +% +% % Load sig +% nps = load_image_set('npsplus'); +% nps = get_wh_image(nps, 1); +% sig_to_plot = nps; +% +% % Plot: +% hh = wedge_plot_by_atlas(sig_to_plot, 'signature') +% hh = wedge_plot_by_atlas(sig_to_plot,'signature','colors',{[0 0 1],[1 0 0]},'montage') +% +% % Try with VPS: +% [sigs, signames] = load_image_set('npsplus'); +% sig_to_plot = get_wh_image(sigs, 7); signames{7} +% hh = wedge_plot_by_atlas(sig_to_plot, 'signature') +% hh = wedge_plot_by_atlas(sig_to_plot,'signature','colors',{[0 0 1],[1 0 0]},'montage') +% +% % Try some data: +% imgs = load_image_set('emotionreg'); +% hh = wedge_plot_by_atlas(imgs, 'atlases', {'cit168' 'brainstem'}); +% +% % Try custom colors, mirroring clusters across L/R hem networks: +% [colors1, colors2] = deal(scn_standard_colors(16)); +% colors = {}; +% indx = 1; +% for i = 1:length(colors1) +% colors{indx} = colors1{i}; colors{indx + 1} = colors2{i}; indx = indx + 2; +% end +% [hh, output_values_by_region, labels, atlas_obj, colorband_colors] = wedge_plot_by_atlas(imgs, 'atlases', {'yeo17networks'}, 'montage', 'colorband_colors', colors); +% +% :Notes on pattern valence: +% +% built into apply_parcellation +% +% Define pattern valence as similarity with unit vector (all positive, +% identical weights). % -% Define pattern valence as similarity with unit vector (all positive, -% identical weights) +% If x is a vector of pattern weights, the cosine similarity with the +% unit vector is a measure of how uniform the weights are, on a scale +% of 1 to -1. 1 indicates that the pattern computes the region average +% (all weights identical and positive). -1 indicates that the pattern +% computes the negative region average (all weights identical and +% negative). % -% If x is a vector of pattern weights, -% The cosine similarity with the unit vector is a measure of how uniform -% the weights are, on a scale of 1 to -1. 1 indicates that the pattern -% computes the region average (all weights identical and positive). -1 -% indicates that the pattern computes the negative region average (all -% weights identical and negative). +% :See also: +% - apply_parcellation +% - tor_wedge_plot +% - load_atlas +% - get_region_volumes diff --git a/CanlabCore/@image_vector/winnerTakeAll.m b/CanlabCore/@image_vector/winnerTakeAll.m index 072cb70e..6085a297 100644 --- a/CanlabCore/@image_vector/winnerTakeAll.m +++ b/CanlabCore/@image_vector/winnerTakeAll.m @@ -1,38 +1,60 @@ function result = winnerTakeAll(input_data) -% winnerTakeAll Identifies the indices of maximum values in each row of the input data from an image_vector object. -% -% Usage: -% result = winnerTakeAll(input_data) -% -% Inputs: -% input_data - A image_vector object containing the data -% from which the max indices are to be found. If the input is a -% 'statistic_image', it will be thresholded at p < 0.1 (uncorrected) -% before finding the max indices. -% -% Outputs: -% result - A structure identical to the input 'input_data', but with its 'dat' field -% replaced by a column vector of indices corresponding to the maximum values -% in each row of the original data matrix. Indices corresponding to rows where -% the maximum value is less than or equal to 0 are set to 0. -% -% Example: -% dat = fmri_data('results.nii'); % A 5x10 matrix of random data -% result = winnerTakeAll(dat); -% % result.dat will contain the indices of the max values in each row -% -% Notes: -% - If the input is a 'statistic_image', its 'dat' field is multiplied by the 'sig' -% field after thresholding. -% - Rows with all non-positive values in 'dat' will have a max index of 0 in 'result.dat'. -% - This function is useful for selecting the most significant (largest) feature -% across multiple conditions or variables for each observation. -% -% Created by: Michael Sun, PhD -% Date: 08/30/2024 -% Version: 1.0 -% -% See also: max, threshold +% winnerTakeAll Identify the column index of the maximum value in each voxel of an image_vector object. +% +% For each voxel (row of input_data.dat), returns the column index +% (image) holding the maximum value across images. Useful for selecting +% the most significant (largest) feature across multiple conditions or +% variables for each observation. Indices for voxels whose maximum is +% non-positive are set to 0. +% +% :Usage: +% :: +% +% result = winnerTakeAll(input_data) +% +% :Inputs: +% +% **input_data:** +% An image_vector object containing the data from which the max +% indices are to be found. If the input is a 'statistic_image', +% it will be thresholded at p < 0.1 (uncorrected) before finding +% the max indices. +% +% :Outputs: +% +% **result:** +% A structure identical to the input 'input_data', but with its +% 'dat' field replaced by a column vector of indices corresponding +% to the maximum values in each row of the original data matrix. +% Indices corresponding to rows where the maximum value is less +% than or equal to 0 are set to 0. +% +% :Examples: +% :: +% +% dat = fmri_data('results.nii'); % A 5x10 matrix of random data +% result = winnerTakeAll(dat); +% % result.dat will contain the indices of the max values in each row +% +% :Notes: +% +% - If the input is a 'statistic_image', its 'dat' field is multiplied +% by the 'sig' field after thresholding. +% - Rows with all non-positive values in 'dat' will have a max index +% of 0 in 'result.dat'. +% - This function is useful for selecting the most significant +% (largest) feature across multiple conditions or variables for each +% observation. +% +% :See also: +% - max +% - threshold +% +% .. +% Created by: Michael Sun, PhD +% Date: 08/30/2024 +% Version: 1.0 +% .. if isa(input_data, 'statistic_image') input_data = threshold(input_data, .1, 'unc'); diff --git a/CanlabCore/@predictive_model/predictive_model_old.m b/CanlabCore/@predictive_model/predictive_model_old.m deleted file mode 100644 index b334ba37..00000000 --- a/CanlabCore/@predictive_model/predictive_model_old.m +++ /dev/null @@ -1,133 +0,0 @@ -classdef predictive_model -% Predictive model object for training and testing brain data -% Example: Fit a linear discriminant to the Fisher iris data, and look at -% a confusion matrix comparing the true and predicted Species values: -% t = readtable('fisheriris.csv','format','%f%f%f%f%C'); -% d = fitcdiscr(t,'Species') -% confusionmat(t.Species,predict(d,t)) -% -% See also fitcdiscr, -% classreg.learning.classif.CompactClassificationDiscriminant. -% -% Methods: -% train -% test -% crossval -% set_training_test -% report -% plot % predictions vs. outcomes -% montage -% select_features -% bootstrap (and reproducibility) -% error_analysis (misclassified images, within/between error) -% -% This is a stub - the start of an incomplete function. For now, use predict.m to -% train models, which returns stats structures. -% -% Properties: -% **nfolds** = 5 -% number of folds -% -% **nfolds** = [vector of integers] -% can also input vector of integers for holdout set IDs -% -% **error_type** = mcr -% mcr, mse: misclassification rate or mean sq. error -% -% **algorithm_name** = 'cv_pcr' -% name of m-file defining training/test function -% -% **useparallel** = 1 -% Use parallel processing, if available; follow by 1 for yes, 0 for no -% -% **bootweights** = 0 -% bootstrap voxel weights; enter bootweights do bootstrapping of weight maps (based on all observations) -% -% **savebootweights** -% save bootstraped weights (useful for combining across multiple iterations of predict()) -% -% **bootsamples** = 100 -% number of bootstrap samples to use -% -% **numcomponents** = xxx: -% save first xxx components (for pca-based methods) -% -% **nopcr** -% for cv_lassopcr and cv_lassopcrmatlab: do not do pcr, use original variables -% -% **lasso_num** = xxx -% followed by number of components/vars to retain after shrinkage -% -% **hvblock** = [h,v] -% use hvblock cross-validation with a block size of 'h' (0 reduces to v-fold xval) and -% number of test observations 'v' (0 reduces to h-block xval) -% -% **rolling** = [h,v,g] -% use rolling cross-validation with a block size of 'h' (0 reduces to v-fold xval) and -% number of test observations 'v' (0 reduces to h-block xval), and a training size -% of g * 2 surrounding hv -% -% **verbose** = 1 -% Set to 0 to suppress output to command window -% -% **platt_scaling** -% calculate cross-validated platt scaling if using SVM. -% Softmax parameters [A,B] are in other_output{3} - - -properties - - % Provenance - training_data_description string = 'Description of training dataset'; - training_data_references string; % Publication(s) - model_description string = 'Description of model'; - model_references string % Publication(s) - - % Before fitting - Y (:, :) double % Outcome [1981 double] - id (:, 1) double % Participant (or grouping) ID for crossval, bootstrapping, permutation - model_type string % Linear multivariate regression, SVM, etc. - algorithm_name %: compatible with predict( ) 'cv_lassopcr' - function_call % : '@(xtrain, ytrain, xtest, cv_assignment) cv_lassopcr(xtrain, ytrain, xtest, cv_assignment, predfun_inputs{:})' - function_handle % [function_handle] - error_type string % : 'mse' - nfolds = 5 % string 'nfolds' or number of folds - cvpartition struct % [11 struct] - teIdx (1, :) cell % Cell: 1 x number of folds: {15 cell} - trIdx (1, :) cell - model_options cell % Inputs passed in during training: {'param_name', value, 'name', value} - - % After fitting - - % data and residuals - yfit (:, 1) double - err (:, 1) logical - cverr (1, 1) double - mse (1, 1) double - rmse (1, 1) double - meanabserr (1, 1) double - pred_outcome_r (1, 1) double - - % Model parameters - weight_obj fmri_data % statistic_image? if bootstrapped - model_intercept (1, 1) double - model_encoding_obj fmri_data % fmri_data object with voxels x test participants (groups) - cv_weight_obj fmri_data - cv_intercept (1, :) - %other_output: {[2237071 double] [38.4627] []} - %other_output_descrip: 'Other output from algorithm - trained on all data (these depend on algorithm)' - % other_output_cv: {53 cell} - % other_output_cv_descrip: 'Other output from algorithm - for each CV fold' - - % bootstrapped - % **** - -end - -methods - - -end - - -end diff --git a/CanlabCore/@region/check_extracted_data.m b/CanlabCore/@region/check_extracted_data.m index f913cd30..4a748712 100644 --- a/CanlabCore/@region/check_extracted_data.m +++ b/CanlabCore/@region/check_extracted_data.m @@ -1,16 +1,38 @@ function isok = check_extracted_data(cl) -% Checks the data, just in case of space/programming issues, -% by re-extracting the region average data from 5 random regions -% using spm_get_data.m, and compares it to the already-saved values +% check_extracted_data Verify region-average data via re-extraction from source images. % -%:Inputs: +% Re-extract the region-average data from 5 randomly chosen regions +% using spm_get_data.m and compare the result to the already-saved +% values in cl.dat. Useful as a sanity check when space/programming +% issues are suspected. +% +% :Usage: +% :: +% +% isok = check_extracted_data(cl) +% +% :Inputs: % % **cl:** -% must be a valid region object (see region.m) -% and cl(1).source_images must still be on the path. +% A valid region-class object (see region.m). cl(1).source_images +% must still be on the path so the original data can be re-read. +% +% :Outputs: % -% You should not need to run this regularly -- but you should if you +% **isok:** +% Logical scalar: true if the correlation between re-extracted +% and stored region averages is greater than 0.999 for every +% sampled region. +% +% :Notes: +% +% You should not need to run this regularly, but you should if you % suspect things have gone awry. +% +% :See also: +% - region +% - spm_get_data +% - extract_roi_averages isok = 1; diff --git a/CanlabCore/@region/isempty.m b/CanlabCore/@region/isempty.m index ca65d423..914cf892 100644 --- a/CanlabCore/@region/isempty.m +++ b/CanlabCore/@region/isempty.m @@ -1,5 +1,27 @@ function is_empty = isempty(obj) - +% isempty Test whether a region object is empty. +% +% Returns true when the region object array has length 0, or when its +% first element has an empty XYZ field. Useful as a guard before +% iterating over regions. +% +% :Usage: +% :: +% +% is_empty = isempty(obj) +% +% :Inputs: +% +% **obj:** +% A region-class object array. +% +% :Outputs: +% +% **is_empty:** +% Logical scalar; true if obj is empty in the sense above. +% +% :See also: +% - region is_empty = length(obj) == 0 || isempty(obj(1).XYZ) || length(obj(1).XYZ) == 0; diff --git a/CanlabCore/@region/isosurface.m b/CanlabCore/@region/isosurface.m index 26dfc3fb..0d593507 100644 --- a/CanlabCore/@region/isosurface.m +++ b/CanlabCore/@region/isosurface.m @@ -1,34 +1,72 @@ function [surface_handles, colors, patch_cell] = isosurface(r, varargin) -% Create a series of surfaces in different colors, one for each region -% - Options for single color +% isosurface Create a series of surfaces in different colors, one per region. % -% [surface_handles, colors, patch_cell] = isosurface(r, [optional arguments]) +% Render each element of a region object array as a 3-D isosurface using +% imageCluster, with options for a single color across regions and for +% matching colors across left/right hemispheres. % -% optional arguments: -% - Any optional inputs to imageCluster, e.g., 'alpha' -% - 'colors', followed by single color in { } or cell array of multiple colors -% - 'nomatchleftright' or 'nosymmetric', do not match colors across hemispheres (left/right) -% Note: The default matches, and may override your colors. +% :Usage: +% :: % -% Examples: -% atlasfile = which('Morel_thalamus_atlas_object.mat'); -% load(atlasfile) +% [surface_handles, colors, patch_cell] = isosurface(r, [optional arguments]) % -% surface_handles = isosurface(r); -% surface_handles = isosurface(r, 'alpha', .5); -% surface_handles = isosurface(r, 'alpha', .5, 'nomatchleftright'); +% :Inputs: % -% view(135, 30); -% lightRestoreSingle; -% lightFollowView; +% **r:** +% A region-class object array. % -% load(which('CIT168_MNI_subcortical_atlas_object.mat')); -% r = atlas2region(atlas_obj); +% :Optional Inputs: % -% surface_handles = isosurface(r, 'alpha', .5, 'color', {[.3 .6 .4] [.5 .4 .2]}); -% surface_handles = isosurface(r, 'alpha', .5, 'color', {[.3 .6 .4] [.5 .4 .2]}, 'nomatchleftright'); -% p = addbrain('hires right'); -% lightFollowView; +% **Any optional inputs to imageCluster:** +% e.g., 'alpha' followed by transparency value, 'sd', or +% 'smoothbox'. +% +% **'colors':** +% Followed by a single color in { } or a cell array of multiple +% colors. +% +% **'nomatchleftright' or 'nosymmetric':** +% Do not match colors across hemispheres (left/right). The +% default matches L/R and may override user-supplied colors. +% +% :Outputs: +% +% **surface_handles:** +% Vector of patch handles, one per region (legacy form; coerces +% Patch objects to doubles). +% +% **colors:** +% Cell array of [r g b] color triplets used for each region. +% +% **patch_cell:** +% Cell array of patch object handles, one per region. +% +% :Examples: +% :: +% +% atlasfile = which('Morel_thalamus_atlas_object.mat'); +% load(atlasfile) +% +% surface_handles = isosurface(r); +% surface_handles = isosurface(r, 'alpha', .5); +% surface_handles = isosurface(r, 'alpha', .5, 'nomatchleftright'); +% +% view(135, 30); +% lightRestoreSingle; +% lightFollowView; +% +% load(which('CIT168_MNI_subcortical_atlas_object.mat')); +% r = atlas2region(atlas_obj); +% +% surface_handles = isosurface(r, 'alpha', .5, 'color', {[.3 .6 .4] [.5 .4 .2]}); +% surface_handles = isosurface(r, 'alpha', .5, 'color', {[.3 .6 .4] [.5 .4 .2]}, 'nomatchleftright'); +% p = addbrain('hires right'); +% lightFollowView; +% +% :See also: +% - atlas/isosurface +% - imageCluster +% - match_colors_left_right k = length(r); diff --git a/CanlabCore/@region/labelled_surface.m b/CanlabCore/@region/labelled_surface.m index 0293b00d..17a57d4e 100644 --- a/CanlabCore/@region/labelled_surface.m +++ b/CanlabCore/@region/labelled_surface.m @@ -1,22 +1,63 @@ function [centroid, p] = labelled_surface(r, varargin) -% labelled_surface - Plots labelled surfaces with centroids and text annotations -% -% Syntax: [centroid, p] = labelled_surface(r, varargin) +% labelled_surface Plot labelled surfaces with centroids and text annotations. % -% Inputs: -% r - Cell array of surfaces to be plotted -% varargin - Additional parameters for customization (e.g., surface_keyword, colormap, font_arguments) +% Render a region object as transparent isosurfaces on top of a brain +% surface, label each region with its short title and a sequential +% number, and return per-region centroid coordinates and the underlying +% brain surface handle. The text label is offset toward the camera if +% 'popout' is requested. % -% Outputs: -% centroid - Cell array of centroid coordinates for each surface -% p - Handle to the brain surface plot +% :Usage: +% :: % -% Example: -% [centroid, p] = labelled_surface(r, 'surface_keyword', 'left_cutaway', 'colormap', @jet, 'font_arguments', {'FontSize', 12, 'FontWeight', 'bold'}, 'popout', true); +% [centroid, p] = labelled_surface(r, [optional inputs]) % -% See also: addbrain, isosurface, format_strings_for_legend - -% Author: Michael Sun, Ph.D. 05/16/2024 +% :Inputs: +% +% **r:** +% A region-class object array whose elements will be rendered. +% +% :Optional Inputs: +% +% **'surface_keyword':** +% Keyword passed to addbrain to draw the underlying brain +% surface. Default: 'transparent_surface'. +% +% **'colormap':** +% Function handle returning an [n x 3] colormap. Default: +% @colorcube. +% +% **'font_arguments':** +% Cell array of additional name/value pairs forwarded to text(), +% e.g., {'FontSize', 12, 'FontWeight', 'bold'}. +% +% **'popout':** +% Logical; if true, text labels are pushed toward the camera so +% they are visible above the surface. Default: false. +% +% :Outputs: +% +% **centroid:** +% Cell array of centroid coordinates for each rendered surface. +% +% **p:** +% Handle to the underlying brain surface plot. +% +% :Examples: +% :: +% +% [centroid, p] = labelled_surface(r, 'surface_keyword', 'left_cutaway', ... +% 'colormap', @jet, 'font_arguments', ... +% {'FontSize', 12, 'FontWeight', 'bold'}, 'popout', true); +% +% :See also: +% - addbrain +% - isosurface +% - format_strings_for_legend +% +% .. +% Author: Michael Sun, Ph.D. 05/16/2024 +% .. % Parse optional inputs parser = inputParser; diff --git a/CanlabCore/@region/match_colors_left_right.m b/CanlabCore/@region/match_colors_left_right.m index 91895c23..d8d1a5f4 100644 --- a/CanlabCore/@region/match_colors_left_right.m +++ b/CanlabCore/@region/match_colors_left_right.m @@ -1,12 +1,65 @@ function [all_colors, leftmatched, rightmatched, midline, leftunmatched, rightunmatched] = match_colors_left_right(r, varargin) -% Given a region object and optional colorfun, generate a list of colors -% for each region, assigning the same color to symmetric regions in left -% and right hemispheres. +% match_colors_left_right Assign matched colors to symmetric L/R regions in a region object. % -% [all_colors, leftmatched, rightmatched, midline, leftunmatched, rightunmatched] = match_colors_left_right(r, varargin) +% Given a region object and an optional color-generating function, +% produce a list of colors for each region such that anatomically +% symmetric left- and right-hemisphere regions share the same color. +% Midline and unmatched regions are colored separately. Symmetry is +% assessed using mm centers and a mutual-nearest-neighbor rule in the +% (y, z) plane. % -% - input custom color function -% [all_colors, leftmatched, rightmatched, midline, leftunmatched, rightunmatched] = match_colors_left_right(r, @(n) custom_colors([1 0 .5], [.5 0 1], n)); +% :Usage: +% :: +% +% [all_colors, leftmatched, rightmatched, midline, leftunmatched, rightunmatched] = ... +% match_colors_left_right(r, [colorfun]) +% +% :Inputs: +% +% **r:** +% A region-class object array. +% +% :Optional Inputs: +% +% **colorfun:** +% Function handle taking an integer n and returning an n x 3 +% cell/array of colors. Default: @scn_standard_colors. Example: +% @(n) custom_colors([1 0 .5], [.5 0 1], n). +% +% :Outputs: +% +% **all_colors:** +% Cell array of colors, one per region in r. +% +% **leftmatched:** +% Region object containing left-hemisphere regions with matched +% right-hemisphere partners. +% +% **rightmatched:** +% Region object containing right-hemisphere regions with matched +% left-hemisphere partners (same length and order as leftmatched). +% +% **midline:** +% Region object containing midline regions. +% +% **leftunmatched:** +% Region object containing left-hemisphere regions without a +% right-hemisphere partner. +% +% **rightunmatched:** +% Region object containing right-hemisphere regions without a +% left-hemisphere partner. +% +% :Examples: +% :: +% +% [all_colors, leftmatched, rightmatched, midline, leftunmatched, rightunmatched] = ... +% match_colors_left_right(r, @(n) custom_colors([1 0 .5], [.5 0 1], n)); +% +% :See also: +% - scn_standard_colors +% - region/isosurface +% - region/montage colorfun = @scn_standard_colors; diff --git a/CanlabCore/@region/merge.m b/CanlabCore/@region/merge.m index 408befdb..139948d0 100644 --- a/CanlabCore/@region/merge.m +++ b/CanlabCore/@region/merge.m @@ -1,13 +1,40 @@ function cl = merge(cl, wh_merge) -% Merge two or more regions together in a region object. -% Combines fields from all clusters in the named series with the first one -% in the series. +% merge Merge two or more regions together within a region object. +% +% Combine the regions indexed by wh_merge into a single region (the +% first one in wh_merge), updating fields as appropriate: text fields +% are concatenated with ' MERGED WITH ' separators, voxel-list fields +% (XYZ, XYZmm, Z, threshold, all_data) are concatenated horizontally, +% the .val field is concatenated vertically, and per-region averages +% (timeseries, contrastdata, dat) are weighted-averaged by voxel count. +% Center and mm_center are recomputed and the merged regions other +% than the first are removed from the array. % % :Usage: % :: % -% wh_merge = [3 4]; -% cl = merge(cl, wh_merge) +% wh_merge = [3 4]; +% cl = merge(cl, wh_merge) +% +% :Inputs: +% +% **cl:** +% A region-class object array. +% +% **wh_merge:** +% Vector of indices into cl identifying the regions to merge. +% The first entry receives the merged result; the remaining +% entries are deleted from cl. +% +% :Outputs: +% +% **cl:** +% Region object array with the merged region in position +% wh_merge(1) and the other merged elements removed. +% +% :See also: +% - region +% - reparse_continguous % % .. % Tor Wager, April 2011 diff --git a/CanlabCore/@region/montage.m b/CanlabCore/@region/montage.m index dad68b06..aa7fd479 100644 --- a/CanlabCore/@region/montage.m +++ b/CanlabCore/@region/montage.m @@ -1,19 +1,28 @@ function o2 = montage(obj, varargin) -% This function displays a region object on a standard slice montage +% montage Display a region object on a standard slice montage. +% +% Render a region object on a slice montage (and optionally surfaces) +% via canlab_results_fmridisplay and addblobs. Supports per-region +% colors, colormaps, indexed colormaps for parcellations, symmetric +% L/R coloring, and one-blob-per-slice 'regioncenters' layouts. % % :Usage: % :: % -% montage(obj, [optional inputs]) +% o2 = montage(obj, [optional inputs]) % -% - takes all optional inputs to canlab_results_fmridisplay and addblobs method +% Takes all optional inputs to canlab_results_fmridisplay and the +% addblobs method. % -% :Input: +% :Inputs: % % **obj:** -% a region object +% A region-class object array. +% +% :Optional Inputs: % -% :Optional Inputs: - see help canlab_results_fmridisplay +% See help canlab_results_fmridisplay for the full list. Common +% options include: % % **o2*** % An existing fmridisplay object, with no keyword strings @@ -133,76 +142,76 @@ % and targetsurface are supported. If you're plotting to volumetric slices this has no effect. % Supported targetsurface = {'fsLR_32k', 'fsaverage_164k'} % -% Other inputs to addblobs (fmridisplay method) are allowed, e.g., 'cmaprange', [-2 2], 'trans' +% Other inputs to addblobs (fmridisplay method) are allowed, e.g., +% 'cmaprange', [-2 2], 'trans'. See help fmridisplay (e.g., 'color', [1 +% 0 0]). % -% See help fmridisplay -% e.g., 'color', [1 0 0] +% :Outputs: % +% **o2:** +% An fmridisplay object with the regions rendered. Pass this back +% in to add or remove blobs. % % :Examples: -% ------------------------------------------------------------------------- % :: -% Example 1: -% % Complete group analysis of a standard dataset -% % Do analysis and prep results region object: -% -% img_obj = load_image_set('emotionreg'); % Load a dataset -% t = ttest(img_obj, .005, 'unc'); % Do a group t-test -% t = threshold(t, .005, 'unc', 'k', 10); % Re-threshold with extent threshold of 10 contiguous voxels -% r = region(t); % Turn t-map into a region object with one element per contig region -% -% Label regions and print a table: -% [r, region_table, table_legend_text] = -% autolabel_regions_using_atlas(r); % Label regions. Can be skipped because 'table' below attempts to do this automatically -% table(r); % Print a table of results using new region names -% -% Display montages in several styles: -% -% montage(r) % A unique color per blob (the default) -% montage(r, 'colormap') % A color-map voxels according to statistic values/intensity -% montage(r, 'color', [1 0 0]) % All blobs in red -% montage(r, 'colormap', 'full') % montage type is 'full', with surfaces -% montage(r, 'colormap', 'regioncenters') % montage type is 'regioncenters', with slices located at the center of each blob (zooms in on blobs) -% -% Use colormap, but with blue colors. see addblobs for more options: -% montage(r, 'colormap', 'maxcolor', [0 0 1], 'mincolor', [.6 .5 .8], 'regioncenters'); -% -% Pass out "o2", an fmridisplay object that has blobs registered, so you -% can remove them and reuse them: -% -% o2 = montage(r, 'regioncenters', 'colormap'); -% o2 = removeblobs(o2); -% o2 = montage(r, o2, 'regioncenters', 'color', [1 0 0]); -% -% Example 2: -% % Extend previous results by creating custom fmridisplay object -% % and adding blobs to that. -% -% o2 = canlab_results_fmridisplay([], 'noverbose'); -% o2 = montage(r, o2); % symmetric colors left/right -% o2 = removeblobs(o2); -% o2 = montage(r, o2, 'map'); -% -% Example 3: -% % create a custom fmridisplay object and display regions from a standard -% parcellation (Glasser 2016 Nature cortical parcellation) -% -% create_figure('slices'); axis off -% o2 = canlab_results_fmridisplay([], 'multirow', 2); -% brighten(.6) -% hcp152t1 = which('HCP-MMP1_on_MNI152_ICBM2009a_nlin.nii'); -% r = region(fmri_data(hcp152t1), 'unique_mask_values'); -% o2 = montage(r, o2, 'wh_montages', 3:4); -% o2 = montage(r(1:20), o2, 'wh_montages', 1:2, 'color', [1 .5 0]); -% -% % Use a different montage type in canlab_results_fmridisplay: -% o2 = montage(r, 'compact2', 'nosymmetric'); -% -% % Plot one region blob per slice on a series of montages. -% o2 = montage(r, 'regioncenters', 'nosymmetric'); - -% edited: Tor, 1/2018, unique color option/default -% Tor 7/2018 .added documentation +% +% % Example 1: Complete group analysis of a standard dataset +% img_obj = load_image_set('emotionreg'); % Load a dataset +% t = ttest(img_obj, .005, 'unc'); % Do a group t-test +% t = threshold(t, .005, 'unc', 'k', 10); % Re-threshold with extent threshold of 10 contiguous voxels +% r = region(t); % Turn t-map into a region object with one element per contig region +% +% % Label regions and print a table: +% [r, region_table, table_legend_text] = autolabel_regions_using_atlas(r); +% table(r); % Print a table of results using new region names +% +% % Display montages in several styles: +% montage(r) % A unique color per blob (the default) +% montage(r, 'colormap') % Color-map voxels by statistic values +% montage(r, 'color', [1 0 0]) % All blobs in red +% montage(r, 'colormap', 'full') % 'full' montage type, with surfaces +% montage(r, 'colormap', 'regioncenters') % 'regioncenters' montage type +% +% % Use colormap with blue colors: +% montage(r, 'colormap', 'maxcolor', [0 0 1], 'mincolor', [.6 .5 .8], 'regioncenters'); +% +% % Reuse o2: +% o2 = montage(r, 'regioncenters', 'colormap'); +% o2 = removeblobs(o2); +% o2 = montage(r, o2, 'regioncenters', 'color', [1 0 0]); +% +% % Example 2: Extend by creating custom fmridisplay object +% o2 = canlab_results_fmridisplay([], 'noverbose'); +% o2 = montage(r, o2); % symmetric colors left/right +% o2 = removeblobs(o2); +% o2 = montage(r, o2, 'map'); +% +% % Example 3: Display Glasser 2016 cortical parcellation +% create_figure('slices'); axis off +% o2 = canlab_results_fmridisplay([], 'multirow', 2); +% brighten(.6) +% hcp152t1 = which('HCP-MMP1_on_MNI152_ICBM2009a_nlin.nii'); +% r = region(fmri_data(hcp152t1), 'unique_mask_values'); +% o2 = montage(r, o2, 'wh_montages', 3:4); +% o2 = montage(r(1:20), o2, 'wh_montages', 1:2, 'color', [1 .5 0]); +% +% % Use a different montage type: +% o2 = montage(r, 'compact2', 'nosymmetric'); +% +% % Plot one region blob per slice on a series of montages: +% o2 = montage(r, 'regioncenters', 'nosymmetric'); +% +% :See also: +% - atlas/montage +% - canlab_results_fmridisplay +% - fmridisplay +% - addblobs +% - match_colors_left_right +% +% .. +% edited: Tor, 1/2018, unique color option/default +% Tor, 7/2018, added documentation +% .. % Defaults and inputs % ----------------------------------------------------------------------- diff --git a/CanlabCore/@region/orthviews.m b/CanlabCore/@region/orthviews.m index fcf39d99..060e0b6f 100644 --- a/CanlabCore/@region/orthviews.m +++ b/CanlabCore/@region/orthviews.m @@ -1,14 +1,35 @@ function orthviews(obj, varargin) -% orthviews(obj, varargin) +% orthviews Display a region object on SPM orthviews via cluster_orthviews. % -% Uses cluster_orthviews.m -% Takes all inputs to cluster_orthviews.m +% Forward the region object and all optional inputs to +% cluster_orthviews and apply the standard CANlab hot/cool colormap to +% the SPM orthviews window. % -% Examples: +% :Usage: +% :: % -% - match colors left/right -% all_colors = match_colors_left_right(obj); -% orthviews(r, all_colors); +% orthviews(obj, [optional inputs]) +% +% :Inputs: +% +% **obj:** +% A region-class object array. +% +% :Optional Inputs: +% +% Any inputs accepted by cluster_orthviews. +% +% :Examples: +% :: +% +% % Match colors left/right +% all_colors = match_colors_left_right(obj); +% orthviews(r, all_colors); +% +% :See also: +% - cluster_orthviews +% - match_colors_left_right +% - spm_orthviews_hotcool_colormap cluster_orthviews(obj, varargin{:}); diff --git a/CanlabCore/@region/posneg_separate.m b/CanlabCore/@region/posneg_separate.m index e81f76ce..e781b420 100644 --- a/CanlabCore/@region/posneg_separate.m +++ b/CanlabCore/@region/posneg_separate.m @@ -1,29 +1,50 @@ function [pcl, ncl] = posneg_separate(cl, varargin) +% posneg_separate Split a region object into positive- and negative-valued sub-regions. +% % Separate a region object (cl) into clusters with positive and negative -% peak values, based on values in .val or .Z field (default = val) -% If a region has both positive and negative values, it will be included in -% both sets pcl (positive) and ncl (negative), with only positive-valued and -% negative-valued subsets included in each, respectively. +% peak values, based on values in the .val or .Z field (default: .val). +% If a region has both positive and negative values, it will be included +% in both sets pcl (positive) and ncl (negative), with only the +% positive-valued and negative-valued subsets included in each, +% respectively. Mixed regions are then re-parsed into contiguous blobs. % % :Usage: % :: % -% [pcl, ncl] = posneg_separate(cl, ['Z']) +% [pcl, ncl] = posneg_separate(cl, ['Z']) +% +% :Inputs: +% +% **cl:** +% A region-class object array. +% +% :Optional Inputs: +% +% **'Z':** +% Use the .Z field instead of .val for separating positive and +% negative values. +% +% :Outputs: +% +% **pcl:** +% Region-class object array containing positive-valued sub-regions. % -% Returns pcl and ncl, region structures with positive- and negative-valued -% peaks, respectively, copied from the original cl input. +% **ncl:** +% Region-class object array containing negative-valued sub-regions. % -% :Optional Input: +% :Notes: % -% **Z:** -% To use .Z field +% You may have to use reparse_continguous to get this to work right. % -% :Note: You may have to use reparse_continguous to get this to work right. +% :Examples: % :: % -% r = reparse_continguous(r); -% [pcl, ncl] = posneg_separate(r); +% r = reparse_continguous(r); +% [pcl, ncl] = posneg_separate(r); % +% :See also: +% - reparse_continguous +% - region.table_simple reparseflag = 0; % need to reparse if mixed clusters myfield = 'val'; diff --git a/CanlabCore/@region/region.m b/CanlabCore/@region/region.m index 5dcd96fe..d66cc143 100644 --- a/CanlabCore/@region/region.m +++ b/CanlabCore/@region/region.m @@ -1,88 +1,105 @@ -% 'region' is a class of objects that contains information on groups of -% voxels ("regions") defined in various ways (contiguous voxels above a -% threshold in an analysis, based on atlases, etc.) -% -% the region class replaces the "clusters" structure in the scnlab toolbox, -% and all or almost all of the "clusters" functions should work for regions -% structures as well. The advantage of 'region' is that the class -% definition can help constrain the use and provide additional error -% checking, etc. +% region Class for groups of voxels defined as anatomical or functional regions. % -% Defining a region and initializing: +% 'region' is a class of objects that contains information on groups of +% voxels ('regions') defined in various ways (contiguous voxels above a +% threshold in an analysis, based on atlases, etc.). % -% *Usage:* -% - r = region(obj1, [obj2], [keywords] ) -% - obj1: [fmri_data/statistic_image object to define regions] -% Can also be char array of image filename -% - obj2: Optional: fmri_data/statistic_image object to extract data from -% - keywords: Optional: 'unique_mask_values' or 'contiguous_regions' -% - 'noverbose' : suppress verbose output +% The region class replaces the 'clusters' structure in the scnlab +% toolbox, and all or almost all of the 'clusters' functions should work +% for region objects as well. The advantage of 'region' is that the +% class definition can help constrain the use and provide additional +% error checking, etc. % -% - cl = region; % generates an empty structure -% % You can add fields yourself if you want to, but best to -% define based on existing file name or image_vector object +% :Usage: +% :: % -% Define regions based on continuous voxel values (in this example image, -% there is only one set of contiguous voxels, so 1 region...) +% r = region(obj1, [obj2], [keywords]) +% cl = region; % generates an empty object % -% - mask_image = which('brainmask.nii'); -% - cl = region(mask_image); +% :Inputs: % -% *Inputs:* +% **obj1:** +% An fmri_data/statistic_image object used to define regions, or +% a char array of an image filename. % -% There are two ways to define which voxels are grouped into a 'region', -% which becomes an element of the region variable cl. +% **obj2:** +% Optional fmri_data/statistic_image object to extract data from. +% Voxel values from obj2 are then stored in the region's .dat +% field. % -% enter 'contiguous_regions' -> group by contiguous blobs +% **keywords:** +% Optional string keyword controlling how voxels are grouped into +% a region. There are two ways to define which voxels are grouped +% into a 'region', which becomes an element of the region +% variable cl: % -% or 'unique_mask_values' -> group by unique values in mask .dat field +% - 'contiguous_regions' (default): group by contiguous blobs. +% - 'unique_mask_values': group by unique values in mask .dat field. % -% *Note:* 'contiguous_regions' uses contiguity/clustering information stored -% in mask.volInfo.cluster, which may not have veridical contiguity info if -% you have manipulated it or incorporated anatomical information in working with -% the mask object, or if you have borrowed the volInfo structure from -% another source in creating it. +% Note: 'contiguous_regions' uses contiguity/clustering information +% stored in mask.volInfo.cluster, which may not have veridical +% contiguity info if you have manipulated it or incorporated +% anatomical information in working with the mask object, or if +% you have borrowed the volInfo structure from another source in +% creating it. % -% *Examples:* +% **'noverbose':** +% Suppress verbose output. % -% Define regions based on continuous values in an anatomical mask: -% - mask_image = which('atlas_labels_combined.img'); -% - cl = region(mask_image, 'unique_mask_values'); +% :Outputs: % -% Resample mask_image to space of data_comb fmri_data object, and extract -% averages. Space is defined by data_comb. -% - cl = extract_roi_averages(data_comb, mask_image, 'unique_mask_values'); +% **r (or cl):** +% A region-class object array. % -% Define regions based on unique voxels values in mask_image, and extract -% data stored in data_comb object, resampled to space of mask_image. -% Space is defined by mask_image: -% - cl = region(mask_image, data_comb, 'unique_mask_values'); +% :Examples: +% :: % -% Reslice mask to space of functional images and define regions based on -% mask values in the functional space (good for extracting data, etc.) -% - mask_image = which('anat_lbpa_thal.img'); -% - mask = fmri_mask_image(mask_image); -% - mask = resample_to_image_space(mask, image_names(1, :)); -% - cl = region(mask); +% % Define regions based on continuous voxel values in an anatomical mask: +% mask_image = which('atlas_labels_combined.img'); +% cl = region(mask_image, 'unique_mask_values'); % -% *Methods* +% % Define a single region from a brain mask: +% mask_image = which('brainmask.nii'); +% cl = region(mask_image); % -% Try typing methods(cl) +% % Resample mask_image to space of data_comb fmri_data object, and +% % extract averages. Space is defined by data_comb. +% cl = extract_roi_averages(data_comb, mask_image, 'unique_mask_values'); % -% methods for 'regions' include: +% % Define regions based on unique voxel values in mask_image, and +% % extract data stored in data_comb object, resampled to space of +% % mask_image. Space is defined by mask_image: +% cl = region(mask_image, data_comb, 'unique_mask_values'); % -% Visualization methods: -% montage, orthviews, surf, etc. +% % Reslice mask to space of functional images and define regions +% % based on mask values in the functional space (good for extracting +% % data, etc.): +% mask_image = which('anat_lbpa_thal.img'); +% mask = fmri_mask_image(mask_image); +% mask = resample_to_image_space(mask, image_names(1, :)); +% cl = region(mask); % -% *Programmers' Notes:* +% :Methods: % -% 8/3/2015 : Tor Wager: Fixed bug when applying region to thresholded -% statistic_image object. Did not consider thresholding. +% Try typing methods(cl). Methods for region objects include +% visualization methods (montage, orthviews, surf, etc.) and conversion +% methods (region2atlas, region2fmri_data, etc.). % -% 5/24/2017: Tor and Phil Kragel: first attempt to make compatible with -% use of enforce_variable_types method. +% :See also: +% - atlas +% - fmri_data +% - statistic_image +% - extract_roi_averages +% - cluster2region % -% 7/2018 : Tor - check for empty mask and skip data extraction if so +% .. +% Programmers' Notes: +% 8/3/2015 : Tor Wager: Fixed bug when applying region to thresholded +% statistic_image object. Did not consider thresholding. +% 5/24/2017: Tor and Phil Kragel: first attempt to make compatible with +% use of enforce_variable_types method. +% 7/2018 : Tor - check for empty mask and skip data extraction if so. +% .. classdef region diff --git a/CanlabCore/@region/region2atlas.m b/CanlabCore/@region/region2atlas.m index c9a72c88..765b0f13 100644 --- a/CanlabCore/@region/region2atlas.m +++ b/CanlabCore/@region/region2atlas.m @@ -1,17 +1,53 @@ function atlas_obj = region2atlas(r, reference_image_name) -% Transform region object r into atlas object atlas_obj, given reference -% image name in the space to resample to +% region2atlas Transform a region object into an atlas object. % -% objout = region2atlas(r, [reference_image_name to sample to]) +% Build an atlas-class object from a region object, optionally +% resampling to the space of a reference image. Each region becomes a +% labeled parcel; voxels are coded with an integer index into the +% region array (or with the region's own .dat values, if available). +% Region shorttitles populate atlas labels and titles populate label +% descriptions. % -% Examples: -% see parcellate_pain_predictive_regions.m -% lindquist2015_pos_region_obj = region2fmri_data(r, obj); -% back to regions to test: -% rtest = region(lindquist2015_pos_region_obj, 'unique_mask_values'); -% orthviews(rtest, 'unique') - -% Tor Wager: edited July 16, 2018 +% :Usage: +% :: +% +% atlas_obj = region2atlas(r, [reference_image_name]) +% +% :Inputs: +% +% **r:** +% A region-class object array. +% +% :Optional Inputs: +% +% **reference_image_name:** +% Filename string or image_vector object specifying the space to +% resample the resulting atlas to. If omitted, the atlas is +% returned in the space of r. +% +% :Outputs: +% +% **atlas_obj:** +% An atlas-class object with .dat coded by region index, .labels +% from r.shorttitle, and .label_descriptions from r.title. +% +% :Examples: +% :: +% +% % see parcellate_pain_predictive_regions.m +% lindquist2015_pos_region_obj = region2fmri_data(r, obj); +% % back to regions to test: +% rtest = region(lindquist2015_pos_region_obj, 'unique_mask_values'); +% orthviews(rtest, 'unique') +% +% :See also: +% - region2fmri_data +% - region2imagevec +% - atlas2region +% +% .. +% Tor Wager: edited July 16, 2018 +% .. has_reference_image = nargin > 1; diff --git a/CanlabCore/@region/region2fmri_data.m b/CanlabCore/@region/region2fmri_data.m index ee92f93c..b1b8a5ba 100644 --- a/CanlabCore/@region/region2fmri_data.m +++ b/CanlabCore/@region/region2fmri_data.m @@ -1,15 +1,50 @@ function objout = region2fmri_data(r, reference_obj) -% Transform region object r into fmri_data object objout, given reference -% fmri_data object reference_obj in the same space as region object +% region2fmri_data Transform a region object into an fmri_data object. % -% objout = region2fmri_data(r, reference_obj) +% Build an fmri_data-class object from a region object. If a reference +% fmri_data object in the same space is supplied, its voxel grid and +% mask are reused; otherwise an internal builder reconstructs the +% volume directly from r(1).dim and r(1).M (the 2025 simple build path). +% Voxel values are taken from r.dat if non-empty, then r.Z, otherwise +% the integer region index. % -% Examples: -% see parcellate_pain_predictive_regions.m -% lindquist2015_pos_region_obj = region2fmri_data(r, obj); -% back to regions to test: -% rtest = region(lindquist2015_pos_region_obj, 'unique_mask_values'); -% orthviews(rtest, 'unique') +% :Usage: +% :: +% +% objout = region2fmri_data(r, [reference_obj]) +% +% :Inputs: +% +% **r:** +% A region-class object array. +% +% :Optional Inputs: +% +% **reference_obj:** +% An fmri_data (or image_vector) object in the same space as r. +% If supplied, the result is built into reference_obj's voxel +% grid. If omitted, a new fmri_data object is constructed from +% scratch using r(1).dim and r(1).M. +% +% :Outputs: +% +% **objout:** +% An fmri_data-class object with .dat populated from the region +% object. +% +% :Examples: +% :: +% +% % see parcellate_pain_predictive_regions.m +% lindquist2015_pos_region_obj = region2fmri_data(r, obj); +% % back to regions to test: +% rtest = region(lindquist2015_pos_region_obj, 'unique_mask_values'); +% orthviews(rtest, 'unique') +% +% :See also: +% - region2atlas +% - region2imagevec +% - fmri_data %objout = fmri_data(); @@ -19,17 +54,17 @@ % fname: 'REMOVED: CHANGED SPACE' % dim: [91 109 91] % dt: [2 0] -% pinfo: [31 double] -% mat: [44 double] +% pinfo: [3�1 double] +% mat: [4�4 double] % n: [1 1] % descrip: 'Space of /Users/tor/Documents/Code_External/spm12/toolbox/FieldMap/bra?' -% private: [11 nifti] +% private: [1�1 nifti] % nvox: 902629 -% image_indx: [9026291 logical] -% wh_inmask: [3523281 double] +% image_indx: [902629�1 logical] +% wh_inmask: [352328�1 double] % n_inmask: 352328 -% xyzlist: [3523283 double] -% cluster: [3523281 double] +% xyzlist: [352328�3 double] +% cluster: [352328�1 double] % Added data mapping. .dat transferred first, otherwise, .Z, followed by % the region number diff --git a/CanlabCore/@region/region2imagevec.m b/CanlabCore/@region/region2imagevec.m index 77fab5a1..ac07599c 100644 --- a/CanlabCore/@region/region2imagevec.m +++ b/CanlabCore/@region/region2imagevec.m @@ -1,17 +1,46 @@ function [ivecobj, orig_idx_vec] = region2imagevec(r, varargin) -% Convert a region object to an image_vector object, replacing the voxels -% and reconstructing as much info as possible. Optional: Resample to the -% space of another image_vector object specified by the first additional input. +% region2imagevec Convert a region object to an image_vector object. % -% The .dat field of the new "ivecobj" is made from the r.all_data field. -% if this is empty, uses r.val field, then r.Z as a backup. -% Mask information is available in ivecobj.volInfo. +% Convert a region object to an image_vector object, replacing the +% voxels and reconstructing as much info as possible. Optionally, +% resample to the space of another image_vector object specified by the +% first additional input. +% +% The .dat field of the new ivecobj is built from the r.all_data field; +% if empty, uses r.val, then r.Z as a backup. Mask information is +% available in ivecobj.volInfo. % % :Usage: % :: % -% [ivecobj, orig_idx] = region2imagevec(r, [image_vector object to resample space to]) +% [ivecobj, orig_idx] = region2imagevec(r, [image_vector object to resample to]) +% +% :Inputs: +% +% **r:** +% A region-class object array. +% +% :Optional Inputs: +% +% **image_vector object:** +% If supplied, the resulting ivecobj is resampled to this +% object's space. +% +% :Outputs: +% +% **ivecobj:** +% An image_vector-class object reconstructed from r, with .dat, +% .volInfo, and contiguous-cluster information populated. +% +% **orig_idx_vec:** +% Vector of original region indices for each voxel in +% ivecobj.dat (useful when later reconstructing per-region +% information). % +% :See also: +% - region2atlas +% - region2fmri_data +% - resample_space % Alt code - would need to expand for all regions % ivecobj = image_vector; diff --git a/CanlabCore/@region/region2imagevec2tmp.m b/CanlabCore/@region/region2imagevec2tmp.m deleted file mode 100644 index 8e95c988..00000000 --- a/CanlabCore/@region/region2imagevec2tmp.m +++ /dev/null @@ -1,67 +0,0 @@ -function [ivecobj, orig_cluster_indx] = region2imagevec2tmp(cl) -% Convert a region object to an image_vector object, replacing the voxels -% and reconstructing as much info as possible. -% -% The .dat field of the new "ivecobj" is made from the cl.all_data field. -% if this is empty, uses cl.val field, then cl.Z as a backup. -% Mask information is available in ivecobj.volInfo. -% -% :Usage: -% :: -% -% ivecobj = region2imagevec(cl) -% -% .. -% NEEDS SOME ADDITIONAL WORK/CHECKING -% .. - -ivecobj = image_vector; -ivecobj.volInfo.mat = cl(1).M; -ivecobj.volInfo.dim = cl(1).dim; - -% no, will be reordered -ivecobj.volInfo.xyzlist = cat(2, cl.XYZ)'; - -mask = clusters2mask2011(cl, cl(1).dim); - -n = sum(mask(:) ~= 0); - -% tor changed april 28 2011 to be all voxels -ivecobj.removed_voxels = mask(:) == 0 | isnan(mask(:)); %false(n, 1); - -ivecobj.volInfo.image_indx = mask(:) ~= 0; -ivecobj.volInfo.n_inmask = n; - -ivecobj.volInfo.wh_inmask = find(ivecobj.volInfo.image_indx); - -% re-get continguity; don't just assume -orig_cluster_indx = mask(:); -orig_cluster_indx = orig_cluster_indx(ivecobj.volInfo.wh_inmask); - -%ivecobj.volInfo.cluster = mask(:); -%ivecobj.volInfo.cluster = ivecobj.volInfo.cluster(ivecobj.volInfo.wh_inmask); - -ivecobj.volInfo.nvox = prod(cl(1).dim); - -[i, j, k] = ind2sub(cl(1).dim, ivecobj.volInfo.wh_inmask); -ivecobj.volInfo.xyzlist = [i j k]; - -if ivecobj.volInfo.n_inmask < 50000 - ivecobj.volInfo.cluster = spm_clusters(ivecobj.volInfo.xyzlist')'; -else - ivecobj.volInfo.cluster = ones(ivecobj.volInfo.n_inmask, 1); -end - -ivecobj.volInfo.dt = [16 0]; % for reslicing compatibility -ivecobj.volInfo.fname = 'Reconstructed from clusters'; - -ivecobj.dat = ones(length(ivecobj.volInfo.wh_inmask), 1); - -% region2imagevec creates illegal list of removed_voxels (doesn't match -% .volInfo.wh_inmask). Fix... -ivecobj.removed_voxels = ivecobj.removed_voxels(ivecobj.volInfo.wh_inmask); - - - - -end diff --git a/CanlabCore/@region/region2imagevec_old.m b/CanlabCore/@region/region2imagevec_old.m deleted file mode 100644 index 44bf288f..00000000 --- a/CanlabCore/@region/region2imagevec_old.m +++ /dev/null @@ -1,86 +0,0 @@ -function [ivecobj, orig_cluster_indx] = region2imagevec(cl) -% Convert a region object to an image_vector object, replacing the voxels -% and reconstructing as much info as possible. -% -% The .dat field of the new "ivecobj" is made from the cl.all_data field. -% if this is empty, uses cl.val field, then cl.Z as a backup. -% Mask information is available in ivecobj.volInfo. -% -% :Usage: -% :: -% -% ivecobj = region2imagevec(cl) -% - - -ivecobj = image_vector; -ivecobj.volInfo.mat = cl(1).M; -ivecobj.volInfo.dim = cl(1).dim; - -% Data in image_vector objects is stored in standard matlab vectorization order -% So cannot assume that values in all_data and XYZ match in order. Must rebuild. - -[~, mask] = clusters2mask2011(cl, cl(1).dim); % 2nd output: Z-field values stored in mask elements -maskvec = mask(:); - -valid_vox = maskvec ~= 0 & ~isnan(maskvec); -n = sum(valid_vox); - -% add data. all_data if we have it, or .val or .Z - -% cannot assume that values in all_data and XYZ match in order. -% ivecobj.dat = cat(2, cl.all_data)'; -% if isempty(ivecobj.dat) || all(ivecobj.dat == 0), ivecobj.dat = cat(1, cl.val); end -% if isempty(ivecobj.dat) || all(ivecobj.dat == 0), ivecobj.dat = cat(2, cl.Z)'; end - -% Wani resorted .dat, but could cause problems if mask(:) does not match. -% Voxel order in .dat is assumed to match mask(:), can't reorder... -% Better to Rebuild XYZ voxel list -% xyz = cat(2,cl.XYZ)'; -% if ~isempty(xyz) -% [dummy, idx1] = sort(xyz(:,1)); -% [dummy, idx2] = sort(xyz(idx1,2)); -% [dummy, idx3] = sort(xyz(idx1(idx2),3)); -% ivecobj.dat = ivecobj.dat(idx1(idx2(idx3)),:); -% end - -[x, y, z] = ind2sub(ivecobj.volInfo.dim, valid_vox); - -% tor changed april 28 2011 to be all voxels -ivecobj.removed_voxels = mask(:) == 0 | isnan(mask(:)); %false(n, 1); - -ivecobj.volInfo.image_indx = mask(:) ~= 0; -ivecobj.volInfo.n_inmask = n; - -ivecobj.volInfo.wh_inmask = find(ivecobj.volInfo.image_indx); - -% re-get continguity; don't just assume -orig_cluster_indx = mask(:); -orig_cluster_indx = orig_cluster_indx(ivecobj.volInfo.wh_inmask); - -%ivecobj.volInfo.cluster = mask(:); -%ivecobj.volInfo.cluster = ivecobj.volInfo.cluster(ivecobj.volInfo.wh_inmask); - -ivecobj.volInfo.nvox = prod(cl(1).dim); - -[i, j, k] = ind2sub(cl(1).dim, ivecobj.volInfo.wh_inmask); -ivecobj.volInfo.xyzlist = [i j k]; - -if ivecobj.volInfo.n_inmask < 50000 - ivecobj.volInfo.cluster = spm_clusters(ivecobj.volInfo.xyzlist')'; -else - ivecobj.volInfo.cluster = ones(ivecobj.volInfo.n_inmask, 1); -end - -ivecobj.volInfo.dt = [16 0]; % for reslicing compatibility -ivecobj.volInfo.fname = 'Reconstructed from clusters'; -% -% ivecobj.dat = cat(2, cl.all_data)'; -% ivecobj.removed_voxels = mask(:) == 0; -% ivecobj.volInfo.image_indx = true(size(ivecobj.removed_voxels)); -% ivecobj.volInfo.n_inmask = length(ivecobj.removed_voxels); -% ivecobj.volInfo.wh_inmask = [1:ivecobj.volInfo.n_inmask ]'; -% ivecobj.volInfo.cluster = mask(:); -% ivecobj.volInfo.nvox = prod(size(cl(1).dim)); - -end diff --git a/CanlabCore/@region/region2struct.m b/CanlabCore/@region/region2struct.m index 85949866..c5a6ff57 100644 --- a/CanlabCore/@region/region2struct.m +++ b/CanlabCore/@region/region2struct.m @@ -1,8 +1,29 @@ function cl = region2struct(cl) -% Convert a region object to a simple structure, primarily for -% compatibility with other, older CANlab tools. +% region2struct Convert a region object to a simple struct array. % -% :See also: cluster2region, for the reverse transformation +% Convert a region object to a simple MATLAB struct array, primarily for +% compatibility with other, older CANlab tools that expect a 'clusters' +% structure rather than a region-class object. +% +% :Usage: +% :: +% +% cl = region2struct(cl) +% +% :Inputs: +% +% **cl:** +% A region-class object array. +% +% :Outputs: +% +% **cl:** +% A struct array containing the same fields as the input region +% object, ready for use with legacy tools. +% +% :See also: +% - cluster2region (the reverse transformation) +% - region warning off for i = 1:length(cl) diff --git a/CanlabCore/@region/reparse_continguous.m b/CanlabCore/@region/reparse_continguous.m index 35b006c0..e43aff75 100644 --- a/CanlabCore/@region/reparse_continguous.m +++ b/CanlabCore/@region/reparse_continguous.m @@ -1,10 +1,35 @@ function clout = reparse_continguous(cl) -% Re-define regions in region object based on contiguous blobs +% reparse_continguous Re-define regions in a region object based on contiguous blobs. +% +% Convert the input region object to an image_vector representation, +% then re-build a region-class object array using contiguous-region +% parsing. If the input regions carry .all_data, the per-voxel data is +% redistributed across the new contiguous regions and re-averaged into +% .dat for each new region. % % :Usage: % :: % -% clout = reparse_continguous(cl) +% clout = reparse_continguous(cl) +% +% :Inputs: +% +% **cl:** +% A region-class object array, typically resulting from +% operations that may have produced regions with non-contiguous +% voxels (e.g., posneg_separate). +% +% :Outputs: +% +% **clout:** +% A new region-class object array with one element per +% contiguous blob, with .all_data and .dat re-distributed where +% possible. +% +% :See also: +% - region2imagevec +% - posneg_separate +% - region % % .. % NEEDS SOME ADDITIONAL WORK/CHECKING diff --git a/CanlabCore/@region/select_coordinates_near_regions.m b/CanlabCore/@region/select_coordinates_near_regions.m index 09b001a3..bc4cb844 100644 --- a/CanlabCore/@region/select_coordinates_near_regions.m +++ b/CanlabCore/@region/select_coordinates_near_regions.m @@ -1,8 +1,44 @@ -function [xyz_out, indx, min_distance] = select_coordinates_near_regions(region_obj, xyz, cutoff_in_mm) -% xyz_out = select_coordinates_near_regions(region_obj, xyz, cutoff_in_mm) +function [xyz_out, indx, min_distance] = select_coordinates_near_regions(region_obj, xyz, cutoff_in_mm) +% select_coordinates_near_regions Filter coordinates by minimum distance to a region object. % -% xyz = r x 2 matrix of XYZ mm coordinates, e.g., DB.xyz from meta-analysis -% cutoff_in_mm: xyz coordinates within this distance from any coordinate in the region object will be saved +% Take an r x 3 matrix of MNI mm coordinates (e.g., a meta-analysis +% coordinate database) and return only those coordinates that lie within +% cutoff_in_mm of any voxel in the supplied region object. +% +% :Usage: +% :: +% +% [xyz_out, indx, min_distance] = select_coordinates_near_regions(region_obj, xyz, cutoff_in_mm) +% +% :Inputs: +% +% **region_obj:** +% A region-class object array. +% +% **xyz:** +% r x 3 matrix of XYZ mm coordinates (e.g., DB.xyz from a +% meta-analysis). +% +% **cutoff_in_mm:** +% Distance threshold (in mm). Coordinates within this distance +% from any coordinate in the region object will be retained. +% +% :Outputs: +% +% **xyz_out:** +% Subset of xyz containing only coordinates within cutoff_in_mm +% of the region object. +% +% **indx:** +% Logical r x 1 vector indicating which rows of xyz were kept. +% +% **min_distance:** +% r x 1 vector of minimum distances from each input coordinate to +% the region object (in mm). +% +% :See also: +% - region +% - dist xyz1 = cat(2, region_obj.XYZmm)'; % region object diff --git a/CanlabCore/@region/table_of_atlas_regions_covered.m b/CanlabCore/@region/table_of_atlas_regions_covered.m index eaffb9cc..52b1723c 100644 --- a/CanlabCore/@region/table_of_atlas_regions_covered.m +++ b/CanlabCore/@region/table_of_atlas_regions_covered.m @@ -1,62 +1,120 @@ function [results_table_pos, results_table_neg, r, excluded_region_table, r_excluded, region_list] = table_of_atlas_regions_covered(r, varargin) -% Make a table of which atlas parcels are covered by a set of regions or map identified in a study +% table_of_atlas_regions_covered Tabulate atlas parcels covered by a set of regions. % -% [results_table_pos, results_table_neg, r, excluded_region_table, r_excluded, region_list] = table_of_atlas_regions_covered(r, [atlas_obj]) +% fMRI activation maps often include large suprathreshold areas that +% span multiple brain regions. Traditional tables divide areas by +% contiguous regions ('blobs'), but this type of division is not useful +% if blobs cannot be easily summarized by a single anatomical label. A +% complementary approach is to identify which areas defined in a +% standard brain atlas ('parcels') are covered by the activation map. +% Parcels can be defined based on anatomy (e.g., cytoarchitecture) or +% function (e.g., resting-state fMRI). For example, a large blob may +% span the insula, claustrum, and putamen. This can be described in a +% table that lists all of these regions, along with the percentage of +% each parcel covered by the activation map. % -% fMRI activation maps often include large suprathreshold areas that span -% multiple brain regions. Traditional tables divide areas by contiguous -% regions ("blobs"), but this type of division is not useful if blobs cannot -% be easily summarized by a single anatomical label. A complementary approach -% is to identify which areas defined in a standard brain atlas ("parcels") -% are covered by the activation map. Parcels can be defined based on anatomy -% (e.g., cytoarchitecture) or function (e.g., resting-state fMRI). -% For example, a large blob may span the insula, claustrum, and putamen. -% This can be described in a table that lists all of these regions, along with -% the percentage of each parcel covered by the activation map. -% -% This function uses the object method subdivide_by_atlas() to create such -% a table, which is dividided into two tables with positive and negative average -% statistic values (results_table_pos, results_table_neg). These are Matlab -% table-class objects. It also returns a region-class object for reference -% and rendering the subdivided blobs on brain slices or surfaces. +% This function uses the object method subdivide_by_atlas() to create +% such a table, which is divided into two tables with positive and +% negative average statistic values (results_table_pos, +% results_table_neg). These are MATLAB table-class objects. It also +% returns a region-class object for reference and rendering the +% subdivided blobs on brain slices or surfaces. % % The default threshold for coverage is 25% or more of the atlas parcel % covered by the activation map. % -% You can enter any atlas-class object as a 2nd argument. If you don't, -% a default atlas object will be used. +% You can enter any atlas-class object as a 2nd argument. If you do +% not, a default atlas object will be used. % -% Examples: -% ----------------------------------------------------------------- -% % Load a thresholded map: -% % (Note: You must have Neuroimaging_Pattern_Masks repo with subfolders on your path) -% % This is a predictive map for drug craving (Koban et al. 2022, Nat Neurosci) +% :Usage: +% :: % -% ncsthr = fmri_data(which('NCS_multithr_001k5_005_05_pruned.nii'), 'noverbose'); -% -% % Convert to region object: -% r = region(ncsthr); +% [results_table_pos, results_table_neg, r, excluded_region_table, ... +% r_excluded, region_list] = table_of_atlas_regions_covered(r, [atlas_obj]) % -% [results_table_pos, results_table_neg, r, excluded_region_table, table_legend_text] = table_of_atlas_regions_covered(r); -% montage(r, 'regioncenters', 'colormap'); +% :Inputs: % -% % Also see large blobs that didn't sufficiently cover any one atlas parcel: -% montage(r_excluded(cat(1, r_excluded.numVox) > 20), 'regioncenters', 'colormap'); +% **r:** +% A region-class object array, typically produced by region(t) +% from a thresholded statistic_image or fmri_data object. % -% % You can use the image_vector method to apply this to a single-image -% % thresholded map: -% [results_table_pos, results_table_neg, r, excluded_region_table, r_excluded, region_labels] = table_of_atlas_regions_covered(ncsthr); +% :Optional Inputs: % -% Note: For a key to labels when using the Glasser 2016 Nature atlas, or the -% CANlab combined atlas (which uses Glasser), see GlasserTableS1.pdf in the -% original paper or CANlab github, or http://braininfo.rprc.washington.edu/ +% **atlas_obj:** +% An atlas-class object used to define parcels. Default: a CANlab +% canlab2018_2mm atlas, loaded via load_atlas. % -% see also region.table, for autolabeling of regions with an atlas and more -% detailed table output. +% :Outputs: % -% Tor Wager, Feb 2023 +% **results_table_pos:** +% MATLAB table summarizing positive-effect regions, with columns +% Region, Volume, XYZ, maxVal, and Coverage. % -% Fixed some minor bugs and improved error handling when there are no regions to display - Michael Sun, 07/13/2023 +% **results_table_neg:** +% MATLAB table summarizing negative-effect regions, with the +% same columns as results_table_pos. +% +% **r:** +% Region-class object array, subdivided by atlas parcels and +% with custom_info2 set to atlas parcel coverage percentages. +% +% **excluded_region_table:** +% MATLAB table of regions excluded from the main table because +% they covered <25% of any atlas parcel or had <2 voxels. +% +% **r_excluded:** +% Region-class object array corresponding to the excluded +% regions in excluded_region_table. +% +% **region_list:** +% 4-cell cell array containing labeled lists of positive and +% negative regions covered (with coverage percentages). +% +% :Examples: +% :: +% +% % Load a thresholded map: +% % (Note: You must have Neuroimaging_Pattern_Masks repo with +% % subfolders on your path.) +% % This is a predictive map for drug craving (Koban et al. 2022, +% % Nat Neurosci) +% ncsthr = fmri_data(which('NCS_multithr_001k5_005_05_pruned.nii'), 'noverbose'); +% +% % Convert to region object: +% r = region(ncsthr); +% +% [results_table_pos, results_table_neg, r, excluded_region_table, table_legend_text] = ... +% table_of_atlas_regions_covered(r); +% montage(r, 'regioncenters', 'colormap'); +% +% % Also see large blobs that did not sufficiently cover any one +% % atlas parcel: +% montage(r_excluded(cat(1, r_excluded.numVox) > 20), 'regioncenters', 'colormap'); +% +% % You can use the image_vector method to apply this to a single-image +% % thresholded map: +% [results_table_pos, results_table_neg, r, excluded_region_table, r_excluded, region_labels] = ... +% table_of_atlas_regions_covered(ncsthr); +% +% :Notes: +% +% For a key to labels when using the Glasser 2016 Nature atlas, or the +% CANlab combined atlas (which uses Glasser), see GlasserTableS1.pdf in +% the original paper or CANlab github, or +% http://braininfo.rprc.washington.edu/ +% +% :See also: +% - region.table (autolabeling of regions with an atlas, more detailed table output) +% - subdivide_by_atlas +% - load_atlas +% - posneg_separate +% +% .. +% Tor Wager, Feb 2023 +% +% Fixed some minor bugs and improved error handling when there are +% no regions to display - Michael Sun, 07/13/2023. +% .. disp('THIS FUNCTION NEEDS SOME LOVE AND DOES NOT CURRENTLY WORK PROPERLY. ') disp('******************************************************************') diff --git a/CanlabCore/@region/table_simple.m b/CanlabCore/@region/table_simple.m index 32cd5fb8..c2a93a8e 100644 --- a/CanlabCore/@region/table_simple.m +++ b/CanlabCore/@region/table_simple.m @@ -1,16 +1,47 @@ function [results_table, results_table_pos, results_table_neg, table_legend_text] = table_simple(r) -% Make a simple table of fMRI results with one row per region +% table_simple Make a simple table of fMRI results with one row per region. % -% [results_table, results_table_pos, results_table_neg, table_legend_text] = table_simple(r) +% This is most useful if you have divided an activation map into blobs +% (regions) separated by defined anatomical parcels (e.g., see +% image_vector.subdivide_by_atlas). The table has columns Region, +% Volume, XYZ, and maxVal (the signed maximum of the .Z field). Regions +% are split into positive- and negative-valued sub-tables based on the +% sign of the maximum .Z value. % -% - This is most useful if you've divided an activation map into blobs (regions) -% separated by defined anatomical parcels. -% - e.g., see image_vector.subdivide_by_atlas +% :Usage: +% :: % -% see also region.table, for autolabeling of regions with an atlas and more -% detailed table output. +% [results_table, results_table_pos, results_table_neg, table_legend_text] = table_simple(r) % -% Tor Wager, April 2021 +% :Inputs: +% +% **r:** +% A region-class object array. +% +% :Outputs: +% +% **results_table:** +% MATLAB table with one row per region (combined pos and neg). +% +% **results_table_pos:** +% Subset of results_table for regions with positive max .Z +% values. +% +% **results_table_neg:** +% Subset of results_table for regions with negative max .Z +% values. +% +% **table_legend_text:** +% Cell array of legend strings describing the columns. +% +% :See also: +% - region.table (autolabeling of regions with an atlas, more detailed table output) +% - posneg_separate +% - subdivide_by_atlas +% +% .. +% Tor Wager, April 2021 +% .. n_cols = 140; % 140 good for HTML reports sep_str = repmat('_', 1, n_cols); % see textwrap diff --git a/CanlabCore/@statistic_image/check_properties.m b/CanlabCore/@statistic_image/check_properties.m index e1fbe3ce..3ed823c3 100644 --- a/CanlabCore/@statistic_image/check_properties.m +++ b/CanlabCore/@statistic_image/check_properties.m @@ -1,11 +1,39 @@ function obj = check_properties(obj) -% Check properties for a statistic-image object. Fill in empty fields if -% needed. +% check_properties Check and fill in empty properties for a statistic_image object. +% +% Fill in empty fields (.p, .ste, .sig) of a statistic_image object if +% needed, expanding stored data to the full in-mask voxel grid using +% volInfo.wh_inmask. % -% obj = check_properties(obj) % ***under construction, do not use yet*** - -% 2018 July : Created by Tor Wager +% +% :Usage: +% :: +% +% obj = check_properties(obj) +% +% :Inputs: +% +% **obj:** +% A statistic_image object whose .p, .ste, and/or .sig fields may +% be empty. The function references obj.volInfo.nvox and +% obj.volInfo.wh_inmask to expand fields onto the full image grid. +% +% :Outputs: +% +% **obj:** +% The input object with empty fields populated to the full voxel +% grid (default values: p = 1, ste = Inf, sig = 0 for voxels +% outside the mask). +% +% :See also: +% - statistic_image +% - replace_empty +% - validateattributes +% +% .. +% 2018 July : Created by Tor Wager +% .. obj_out = replace_empty(obj_out); k = size(obj_out.dat, 2); diff --git a/CanlabCore/@statistic_image/conjunction.m b/CanlabCore/@statistic_image/conjunction.m index 29e49873..47d186be 100644 --- a/CanlabCore/@statistic_image/conjunction.m +++ b/CanlabCore/@statistic_image/conjunction.m @@ -1,38 +1,66 @@ function conj = conjunction(si1, si2, direction, type) -% Returns the conjunction of two statistic_images. considers positive and +% conjunction Compute the conjunction of two thresholded statistic_image objects. +% +% Returns the conjunction of two statistic_images. Considers positive and % negative activations separately. % +% :Usage: +% :: +% +% conj = conjunction(si1, si2) +% conj = conjunction(si1, si2, direction) +% conj = conjunction(si1, si2, direction, type) +% % :Inputs: % -% Two thresholded statistic images. Optional 3rd argument: -1 to -% get only negative conjunction, or 1 to get only positive conjunction +% **si1, si2:** +% Two thresholded statistic_image objects in the same image space. +% +% **direction:** +% (Optional) -1 to get only negative conjunction, +1 to get only +% positive conjunction, or 0 (default) for both. Empty input is +% allowed and treated as 0. Previously this argument was unnamed; +% it was renamed to 'direction' (Zizhuang Miao, Oct. 2023). +% +% **type:** +% (Optional) Allowed values are 'indicator' or 'values'. +% 'indicator' will return an output with voxel values set to 1 +% and -1 (as previously designed); 'values' will return the +% average voxel values of the two input maps. Default is 'values' +% to prevent crashing previous scripts using this function. +% +% :Outputs: +% +% **conj:** +% A statistic_image with all voxels suprathreshold (in the same +% direction) in both input images. With type 'indicator', voxel +% values are set to 1 and -1 to indicate direction. With type +% 'values', voxel values are the average of the two input maps, +% zeroed outside the conjunction. .p is replaced with a notice +% because it is not valid for a conjunction. % -% :Output: +% :Examples: +% :: % -% A statistic_image with all voxels suprathreshold (in the same direction) in both input -% images. Voxel values are set to 1 and -1, to indicate direction. +% conj = conjunction(si1, si2); % both directions, value map +% conj = conjunction(si1, si2, 1); % positive-only conjunction +% conj = conjunction(si1, si2, -1, 'indicator'); % negative-only indicator map +% +% :See also: +% - statistic_image +% - threshold % % .. % Yoni Ashar, Sept. 2015 -% .. -% -% .. -% Zizhuang Miao, Oct. 2023 -% .. -% -% :Inputs: % -% Changed the name of the previous optional 3rd argument to "direction" +% Zizhuang Miao, Oct. 2023 +% - Renamed the previous optional 3rd argument to 'direction'. +% - Added optional argument 'type' with values 'indicator' or 'values'. % -% Added another optional argument "type", allowed values are "indicator" -% or "values". "indicator" will return an output with voxel values set to 1 -% and -1 (as previously designed); "values" will return the average voxel -% values of the two input maps. Default to "values" to prevent crashing -% previous scripts using this function. -% -% Fixed bug where .sig was not being masked between si1 and si2, leading -% to an overly liberal conjunction map. -% Michael Sun Ph.D., 12/11/2025 +% Michael Sun Ph.D., 12/11/2025 +% - Fixed bug where .sig was not being masked between si1 and si2, +% leading to an overly liberal conjunction map. +% .. if nargin == 2 type = "values"; % default: average of two maps diff --git a/CanlabCore/@statistic_image/convert2mask.m b/CanlabCore/@statistic_image/convert2mask.m index 7bf4efc5..ea99e2f8 100644 --- a/CanlabCore/@statistic_image/convert2mask.m +++ b/CanlabCore/@statistic_image/convert2mask.m @@ -1,12 +1,36 @@ function mask = convert2mask(stats_image_obj) +% convert2mask Convert a statistic_image into an fmri_mask_image based on .sig. +% % Converts each image in a statistic_image object into a mask object, based % on significant voxels in the .sig field. % +% :Usage: +% :: +% +% mask = convert2mask(stats_image_obj) +% +% :Inputs: % -% :Example: +% **stats_image_obj:** +% A statistic_image object. The .sig field (logical) determines +% which voxels are retained as nonzero in the output mask. +% +% :Outputs: +% +% **mask:** +% An fmri_mask_image object with .dat = double(stats_image_obj.sig). +% Other fields including .removed_images and .removed_voxels are +% copied over from the input. +% +% :Examples: % :: % % cl = region(convert2mask(timg), group) +% +% :See also: +% - statistic_image +% - fmri_mask_image +% - region mask = fmri_mask_image(stats_image_obj); % Copy into a mask image diff --git a/CanlabCore/@statistic_image/estimateBayesFactor.m b/CanlabCore/@statistic_image/estimateBayesFactor.m index fb5f5a99..65e793d0 100644 --- a/CanlabCore/@statistic_image/estimateBayesFactor.m +++ b/CanlabCore/@statistic_image/estimateBayesFactor.m @@ -1,64 +1,45 @@ function BF = estimateBayesFactor(stat_image,varargin) -% Compute voxel-wise Bayes Factors from a statistic image object. +% estimateBayesFactor Compute voxel-wise Bayes Factors from a statistic_image object. % % This code is a wrapper function for code from Sam Schwarzkopf at UCL % (see https://figshare.com/articles/Bayes_Factors_Matlab_functions/1357917), -% it computes Bayes Factors for t-tests (Rouder et al., 2009), simple -% correlations (Wetzels et al., 2012), and proportions (http://pcl.missouri.edu/bf-binomial) -% See below for more information. +% it computes Bayes Factors for t-tests (Rouder et al., 2009), simple +% correlations (Wetzels et al., 2012), and proportions +% (http://pcl.missouri.edu/bf-binomial). See below for more information. % -% Usage: +% :Usage: % :: % -% BF = estimateBayesFactor(stat_image,varargin) -% -% .. -% Author and copyright information: -% -% Copyright (C) 2018 Philip Kragel -% -% This program is free software: you can redistribute it and/or modify -% it under the terms of the GNU General Public License as published by -% the Free Software Foundation, either version 3 of the License, or -% (at your option) any later version. -% -% This program is distributed in the hope that it will be useful, -% but WITHOUT ANY WARRANTY; without even the implied warranty of -% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -% GNU General Public License for more details. -% -% You should have received a copy of the GNU General Public License -% along with this program. If not, see . -% .. +% BF = estimateBayesFactor(stat_image, varargin) % % :Background: % % Bayesian t-tests % ----------------------------------------------------------------- % We use the BF Bayes' Factor Matlab toolbox, based on: -% +% % Rouder et al., 2009 % one-sample t-test, t1smpbf.m % Rouder et al., 2009 % binomial test, binombf.m % Wetzels et al., 2012 % Pearson's r, corrbf.m % Boekel et al., 2014 % test for replicating Pearson's r in same direction -% -% Implemented by Sam Schwarzkopf, UCL +% +% Implemented by Sam Schwarzkopf, UCL. % % Rouder (2009) derived a formula to calculate Bayes Factors for a -% one-sample t-test, a common test statistic in neuroimaging, particularly -% when testing contrast or 2nd-level (across-participant) summary statistics +% one-sample t-test, a common test statistic in neuroimaging, particularly +% when testing contrast or 2nd-level (across-participant) summary statistics % in a two-level hierarchical model. They also provided a web application: % http://pcl.missouri.edu/bf-one-sample % -% Gnen et al. (2005) provided the corresponding equation +% Gonen et al. (2005) provided the corresponding equation % for the unit-information Bayes factor. Liang et al. (2008) % provided the corresponding JZS Bayes factors for testing % slopes in a regression model. -% -% As Rouder et al. point out: "Researchers need only provide the sample size N and +% +% As Rouder et al. point out: 'Researchers need only provide the sample size N and % the observed t value. There is no need to input raw data. % The integration is over a single dimension and is computationally -% straightforward." +% straightforward.' % % This function iterates over voxels to calculate bayes factors for a map. % @@ -68,12 +49,12 @@ % prior belief about the effect size, which is integrated with evidence % from the data to estimate a posterior probabilities of both null and % alternative hypotheses. The Bayes Factor (BF) is a ratio of these (see below). -% +% % Different choices of prior distribution and effect size will thus yield % different results for BFs, but there are some standard, reasonable % choices. In addition, BFs are often not very sensitive to reasonable variation % in priors, so it is reasonable to use a default choice for many applications. -% +% % The tests in the BF toolbox use default scaling values for prior distributions, and the % Jeffrey-Zellner-Siow Prior (JZS, Cauchy distribution on effect size). % This is standard, widely used prior. The JZS prior has heavier tails than @@ -108,68 +89,87 @@ % % These are returned in a statistic_image object BF, whose .dat field % contains 2*ln(BF) values for each voxel. A value of about 4.6 indicates -% a BF of 10, or 10:1 evidence in favor of the Alternative, which is a typical cutoff. +% a BF of 10, or 10:1 evidence in favor of the Alternative, which is a typical cutoff. % A value of about 6 indicates 20:1 evidence in favor of the Alternative. -% -% +% % :Inputs: % % **stat_image:** % A statistic image that either contains 1) a t-map, 2) a -% correlation map, or 3) a map of counts for testing proportions +% correlation map, or 3) a map of counts for testing proportions. +% +% :Optional Inputs: % % **input_type:** -% A string specifying the type of map provided for now, -% options are 't', 'r', and 'prop' +% A string specifying the type of map provided. For now, options +% are 't', 'r', and 'prop'. Default: 't'. % % **numeric (optional):** -% Scaling factor r for effect size prior if 't' map is specified. -% +% Scaling factor r for effect size prior if a 't' map is +% specified. Defaults to 0.707 inside t1smpbf.m. % % :Outputs: % % **BF:** -% fmri_data object with voxel-wise bayes factors (scaled as 2 ln BF) -% these can be thresholded according to Kass and Raftery 1995 +% fmri_data object with voxel-wise bayes factors (scaled as +% 2 ln BF). These can be thresholded according to Kass and +% Raftery 1995. % % :Examples: % :: % -% % Example 1: Emotion Regulation data and perform 1-sample t-test +% % Example 1: Emotion Regulation data and perform 1-sample t-test % -% dat=load_image_set('emotionreg'); -% t=ttest(dat); -% BF_tstat=estimateBayesFactor(t,'t'); -% BF_tstat_th = threshold(BF_tstat, [-6 6], 'raw-outside'); -% orthviews(BF_tstat_th); +% dat = load_image_set('emotionreg'); +% t = ttest(dat); +% BF_tstat = estimateBayesFactor(t, 't'); +% BF_tstat_th = threshold(BF_tstat, [-6 6], 'raw-outside'); +% orthviews(BF_tstat_th); % +% % Example 2: Emotion Regulation data and test of proportions % -% -% % Example 1: Emotion Regulation data and test of proportions -% -% dat=load_image_set('emotionreg'); -% t=ttest(dat); -% prop=t; %initialize stats object from t-test output -% prop.dat=sum(dat.dat'>0)'; -% BF_prop=estimateBayesFactor(prop,'prop'); %estimate BF -% BF_prop_th = threshold(BF_prop, [-6 6], 'raw-outside'); -% orthviews(BF_prop_th); -% +% dat = load_image_set('emotionreg'); +% t = ttest(dat); +% prop = t; % initialize stats object from t-test output +% prop.dat = sum(dat.dat'>0)'; +% BF_prop = estimateBayesFactor(prop, 'prop'); % estimate BF +% BF_prop_th = threshold(BF_prop, [-6 6], 'raw-outside'); +% orthviews(BF_prop_th); % % :See also: +% - statistic_image +% - fmri_data +% - t1smpbf +% - corrbf +% - binombf % -% stat_image, fmri_data - % .. +% Author and copyright information: +% +% Copyright (C) 2018 Philip Kragel +% +% This program is free software: you can redistribute it and/or modify +% it under the terms of the GNU General Public License as published by +% the Free Software Foundation, either version 3 of the License, or +% (at your option) any later version. +% +% This program is distributed in the hope that it will be useful, +% but WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +% GNU General Public License for more details. +% +% You should have received a copy of the GNU General Public License +% along with this program. If not, see . +% % Programmers' notes: % List dates and changes here, and author of changes % -% Phil: created, Dec 2018 +% Phil: created, Dec 2018 % -% TODO: add optional input for base-rate when testing proportions +% TODO: add optional input for base-rate when testing proportions % -% 11/21/2019 : Tor Wager added documentation and explanation to help. -% 05/18/2022 : Michael Sun added optional input for r scaling factor when t1smpbf() +% 11/21/2019 : Tor Wager added documentation and explanation to help. +% 05/18/2022 : Michael Sun added optional input for r scaling factor when t1smpbf() % .. if isempty(varargin) diff --git a/CanlabCore/@statistic_image/orthviews.m b/CanlabCore/@statistic_image/orthviews.m index 93995f1f..607984c6 100644 --- a/CanlabCore/@statistic_image/orthviews.m +++ b/CanlabCore/@statistic_image/orthviews.m @@ -1,26 +1,52 @@ function cl = orthviews(image_obj, varargin) -% Orthviews display (SPM) for CANlab object +% orthviews Orthviews display (SPM) for a statistic_image object. +% +% Displays each image in a statistic_image as an SPM orthviews panel, +% honoring the .sig field so only suprathreshold voxels are shown. % % :Usage: % :: % -% cl = orthviews(image_object) +% cl = orthviews(image_object) +% cl = orthviews(image_object, handle_number of existing orthviews) % -% % OR +% :Features: % -% cl = orthviews(image_object, handle_number of existing orthviews) +% - Uses cluster_orthviews.m +% - Will take inputs to cluster_orthviews +% - e.g., orthviews(pstat, 'unique', 'solid'); % -% :Features: -% - Uses cluster_orthviews.m -% - Will take inputs to cluster_orthviews -% - e.g., orthviews(pstat, 'unique', 'solid'); +% Output is clusters structure (see also region.m). +% +% :Inputs: +% +% **image_obj:** +% A statistic_image object. Each column of .dat is rendered in a +% separate orthviews panel; .sig is used as a mask if non-empty. +% +% :Optional Inputs: +% +% **'handle' / 'han' / 'input_handle':** +% Followed by the handle number(s) of an existing orthviews +% figure to display into instead of opening a new one. +% +% **'largest_region':** +% Center the orthviews crosshair on the largest region in the +% first image. +% +% **'overlay':** +% Followed by a filename to use as the underlay image. Default: +% which('fmriprep20_template.nii.gz'). % -% Output is clusters structure (see also region.m) +% Additional optional arguments are passed through to cluster_orthviews. % -% Pass in 'largest_region' to center the orthviews on the largest region in the image +% :Outputs: % +% **cl:** +% Cell array of clusters structures, one per displayed image +% (see also region.m). % -% :Example: +% :Examples: % :: % % % T-test, Construct a stats_image object, threshold and display: @@ -43,7 +69,10 @@ % statsimg = threshold(statsimg, .000001, 'unc'); % orthviews(statsimg, 'handle', 2); % -% :See also: statistic_image.multi_threshold +% :See also: +% - statistic_image.multi_threshold +% - cluster_orthviews +% - region input_handle = []; cl = []; diff --git a/CanlabCore/@statistic_image/reparse_contiguous.m b/CanlabCore/@statistic_image/reparse_contiguous.m index f038b14e..dc54518e 100644 --- a/CanlabCore/@statistic_image/reparse_contiguous.m +++ b/CanlabCore/@statistic_image/reparse_contiguous.m @@ -1,25 +1,42 @@ function obj = reparse_contiguous(obj, varargin) +% reparse_contiguous Re-build the contiguous-cluster index for a statistic_image. +% % Re-construct list of contiguous voxels in an image based on in-image -% voxel coordinates. Coordinates are taken from obj.volInfo.xyzlist. +% voxel coordinates. Coordinates are taken from obj.volInfo.xyzlist. % Results are saved in obj.volInfo.cluster. % xyzlist can be generated from iimg_read_img, and is done automatically by % object-oriented fMRI image classes (fmri_image, image_vector, -% statistic_image) +% statistic_image). % % :Usage: % :: % -% obj = reparse_contiguous(obj, ['nonempty']) -% -% If 'nonempty' is entered as an optional argument, will use only voxels -% that are non-zero, non-nan in the first column of obj.dat. +% obj = reparse_contiguous(obj, ['nonempty']) % -% The statistic_image object version of reparse_contiguous uses +% The statistic_image object version of reparse_contiguous uses % the significance of the first image in the object (obj.sig(:, 1)) as a % filter as well, so clustering will be based on the latest threshold applied. -% it is not usually necessary to enter 'nonempty'. +% It is not usually necessary to enter 'nonempty'. +% +% :Inputs: +% +% **obj:** +% A statistic_image object whose .volInfo.cluster will be +% regenerated using spm_clusters on .volInfo.xyzlist. +% +% :Optional Inputs: +% +% **'nonempty':** +% If entered, will use only voxels that are non-zero, non-nan in +% the first column of obj.dat (in addition to the .sig filter). % -% :Example: +% :Outputs: +% +% **obj:** +% Object with obj.volInfo.cluster reassigned. Voxels outside +% .sig (or excluded by 'nonempty') are zeroed in .cluster. +% +% :Examples: % :: % % % Given timg, a statistic_image object: @@ -27,14 +44,19 @@ % cl = region(test, 'contiguous_regions'); % cluster_orthviews(cl, 'unique') % +% :See also: +% - statistic_image +% - region +% - spm_clusters +% % .. % Copyright tor wager, 2011 - +% % Programmers' notes: % 6/22/14: Tor changed behavior to use .sig field. Returns clusters of % in-mask, significant (.sig) voxels only. -% % 7/2018 - tor - fixed bug with statistic_image handling in some cases -% tor - clusters not in .sig were not zeroed out - now they are +% 7/2018 - tor - fixed bug with statistic_image handling in some cases +% - tor - clusters not in .sig were not zeroed out - now they are % .. wh = true(size(obj.volInfo.cluster)); %obj.volInfo.wh_inmask; diff --git a/CanlabCore/@statistic_image/select_one_image.m b/CanlabCore/@statistic_image/select_one_image.m index 0098090d..afba0f92 100644 --- a/CanlabCore/@statistic_image/select_one_image.m +++ b/CanlabCore/@statistic_image/select_one_image.m @@ -1,14 +1,33 @@ function obj = select_one_image(obj, wh) +% select_one_image Select a single image from a multi-image statistic_image object. % -% Selects one image out of a set of images stored in a statistic_image object +% Selects one image out of a set of images stored in a statistic_image +% object. Reduces .p, .ste, .sig, and .dat to a single column and resets +% .removed_images to 0. +% +% :Usage: +% :: +% +% obj = select_one_image(obj, wh) % % :Inputs: % % **obj:** -% A statistic_image object +% A statistic_image object. % % **wh:** -% An integer for which image in a series of images stored in obj you want +% An integer for which image in a series of images stored in obj +% you want. +% +% :Outputs: +% +% **obj:** +% The input object reduced to the single image indexed by wh, +% across the .p, .ste, .sig, and .dat fields. +% +% :See also: +% - statistic_image +% - get_wh_image myfields = {'p' 'ste' 'sig' 'dat'}; diff --git a/CanlabCore/@statistic_image/statistic_image.m b/CanlabCore/@statistic_image/statistic_image.m index d436ac73..ed0e3679 100644 --- a/CanlabCore/@statistic_image/statistic_image.m +++ b/CanlabCore/@statistic_image/statistic_image.m @@ -1,118 +1,125 @@ -% statistic_image: An object that allows for storage and manipulation of -% images containing t-values, p-values, and thresholded results +% statistic_image Object class for statistical images (t/p/thresholded maps). +% +% An object that allows for storage and manipulation of images containing +% t-values, p-values, and thresholded results. +% % - multiple images can be stored in a single object % - this is a subclass of image_vector and inherits all its methods % - easy thresholding without changing underlying image (threshold method) % - easy visualization (orthviews, surface, and montage methods) % -% Usage: -% ------------------------------------------------------------------------- -% object constructor: this function creates an object and populates it -% with data. Entering image name(s) loads in the data. -% -% obj = statistic_image(varargin) -% obj = statistic_image(nifti/img filename, or allowed fields followed by values) +% :Usage: +% :: +% +% obj = statistic_image(varargin) +% obj = statistic_image(nifti/img filename, or allowed fields followed by values) +% +% The object constructor creates an object and populates it with data. +% Entering image name(s) loads in the data. % % For objects: Type methods(object_name) for a list of special commands % Type help object_name.method_name for help on specific % methods. % -% Author and copyright information: -% ------------------------------------------------------------------------- -% Copyright (C) 2010 Tor Wager +% :Inputs: +% +% **properties:** +% Any valid attribute/property of statistic_image, followed by +% data values to store. See properties(statistic_image). +% +% **image names:** +% A character array with one or more filenames for Analyze/NIFTI +% images (.img/nii). +% +% :Outputs: +% +% **obj:** +% A statistic_image object. +% +% :Examples: +% :: +% +% % As with any object class in this toolbox, you can create an object by +% % specifying names of fields paired with values. You can also enter +% % filenames when you call statistic_image and create an image by loading a +% % file. Finally, some methods performed on other objects (e.g., predict, +% % regress, and ttest for fmri_data objects) will return statistic_image +% % objects. +% % For example, the line of code below creates a map of random t-values: +% t = statistic_image('dat', rand(1000, 1), 'type', 't', 'dfe', 29); +% +% % If you want to use the full functionality of the object type without +% % errors, however, a .volInfo field will need to be attached with +% % information about the image space. The easiest way to create this is to +% % load an existing image, which will automatically read in the space along +% % with other info: +% +% % Read in a p-value image as a p-value object type: +% p = which('nonnoc_v11_4_137subjmap_weighted_pvalue.nii'); +% pstat = statistic_image(p, 'type', 'p'); +% pstat = threshold(pstat, .05, 'fdr'); % -% This program is free software: you can redistribute it and/or modify -% it under the terms of the GNU General Public License as published by -% the Free Software Foundation, either version 3 of the License, or -% (at your option) any later version. +% % Start with the name of a statistic image we're interested in, with arbitrary values +% name = 'salientmap.nii'; % -% This program is distributed in the hope that it will be useful, -% but WITHOUT ANY WARRANTY; without even the implied warranty of -% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -% GNU General Public License for more details. +% % Load it into a statistic_image object +% img = statistic_image('image_names', name); % -% You should have received a copy of the GNU General Public License -% along with this program. If not, see . +% % Plot a histogram of its values +% figure; histogram(img) % -% Inputs: -% ------------------------------------------------------------------------- -% :properties: any valid attribute/property of statistic_image, followed by -% data values to store -% - see properties(statistic_image) +% % Threshold the image values; save values < 0 or > 5 +% img = threshold(img, [0 5], 'raw-outside'); % -% :image names: a character array with one or more filenames for -% Analyze/NIFTI images (.img/nii). +% % Plot a montage and spm-orthviews of the thresholded image +% montage(img); +% orthviews(img); % +% % Remove the threshold; include all values between -Inf and Inf +% img = threshold(img, [-Inf Inf], 'raw-between'); % -% Outputs: -% ------------------------------------------------------------------------- -% obj A statistic_image object +% % Show the orthviews plot of the unthresholded image +% orthviews(img); % -% Examples: -% ------------------------------------------------------------------------- -% As with any object class in this toolbox, you can create an object by -% specifying names of fields paired with values. You can also enter -% filenames when you call statistic_image and create an image by loading a -% file. Finally, some methods performed on other objects (e.g., predict, -% regress, and ttest for fmri_data objects) will return statistic_image -% objects. -% For example, the line of code below creates a map of random t-values: -% t = statistic_image('dat', rand(1000, 1), 'type', 't', 'dfe', 29); +% % You can force an image to be of a particular type, e.g., 'T', in which +% % case p-values will automatically be added. This is useful for +% % thresholding. +% % e.g., for an SPM T-map: +% wh = 1; +% load SPM +% img = sprintf('spmT_00%02d.img', wh(1)) +% t = statistic_image('image_names', img, 'type', 'T', 'dfe', SPM.xX.erdf); % -% If you want to use the full functionality of the object type without -% errors, however, a .volInfo field will need to be attached with -% information about the image space. The easiest way to create this is to -% load an existing image, which will automatically read in the space along -% with other info: +% % If 'type' is 'robreg', then assumes user is in a robust regression +% % directory and will load in the robust_beta_000X.img and load in +% % robust_p_000X.img as the p values. X is assumed = 1, but can be +% % overloaded by passing in a dat_descrip field. i.e. +% deltadon = statistic_image('dat_descrip', 4, 'type', 'robreg') % -% Read in a p-value image as a p-value object type: -% p = which('nonnoc_v11_4_137subjmap_weighted_pvalue.nii'); -% pstat = statistic_image(p, 'type', 'p'); -% pstat = threshold(pstat, .05, 'fdr'); +% :See also: +% - image_vector +% - fmri_data +% - region +% - fmridisplay % -% Start with the name of a statistic image we're interested in, with arbitrary values -% name = 'salientmap.nii'; -% -% % Load it into a statistic_image object -% img = statistic_image('image_names', name); -% -% % Plot a histogram of its values -% figure; histogram(img) -% -% % Threshold the image values; save values < 0 or > 5 -% img = threshold(img, [0 5], 'raw-outside'); -% -% % Plot a montage and spm-orthviews of the thresholded image -% montage(img); -% orthviews(img); -% -% % Remove the threshold; include all values between -Inf and Inf -% img = threshold(img, [-Inf Inf], 'raw-between'); -% -% % Show the orthviews plot of the unthresholded image -% orthviews(img); +% .. +% Author and copyright information: % -% You can force an image to be of a particular type, e.g., 'T', in which -% case p-values will automatically be added. This is useful for -% thresholding. -% e.g., for an SPM T-map: -% wh = 1; -% load SPM -% img = sprintf('spmT_00%02d.img', wh(1)) -% t = statistic_image('image_names', img, 'type', 'T', 'dfe', SPM.xX.erdf); +% Copyright (C) 2010 Tor Wager % -% If 'type' is 'robreg', then assumes user is in a robust regression -% directory and will load in the robust_beta_000X.img and load in -% robust_p_000X.img as the p values. X is assumed = 1, but can be -% overloaded by passing in a dat_descrip field. i.e. -% deltadon=statistic_image('dat_descrip', 4, 'type', 'robreg') +% This program is free software: you can redistribute it and/or modify +% it under the terms of the GNU General Public License as published by +% the Free Software Foundation, either version 3 of the License, or +% (at your option) any later version. % +% This program is distributed in the hope that it will be useful, +% but WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +% GNU General Public License for more details. % -% See also: -% image_vector.m -% fmri_data.m -% region.m -% fmridisplay.m +% You should have received a copy of the GNU General Public License +% along with this program. If not, see . +% .. % classdef statistic_image < image_vector diff --git a/CanlabCore/Cluster_contig_region_tools/cluster_table_old.m b/CanlabCore/Cluster_contig_region_tools/cluster_table_old.m deleted file mode 100644 index 6b552d88..00000000 --- a/CanlabCore/Cluster_contig_region_tools/cluster_table_old.m +++ /dev/null @@ -1,288 +0,0 @@ -% function cluster_table_old(clusters,[opt] subclusters) -% Print output of clusters in table -% Tor Wager -% -% Option to print text labels from Carmack atlas -% Database loading is done from talaraich_info.mat -% which should be in the path. -% To speed up performance, declare global xyz, L3 and L5 -% in the base workspace and re-use them in repeated calls -% to cluster_table. -function clusters = cluster_table_old(clusters,varargin) - -global xyz L3 L5 - -if isfield(clusters,'M'),M = clusters(1).M;, end -if length(varargin) > 0, subc = varargin{1};, end - -% try to load table with BAs. Brodmann Areas. -% Old: for ICBM and old Talairach atlas. -% -% if isfield(clusters,'BAstring') -% strs = str2mat(clusters.BAstring); -% else -% try -% V = spm_vol(which('Tal_gray.img')); v = spm_read_vols(V); -% V.M = V.mat; -% -% % try to get BA composition for all clusters -% %diary off -% %disp('Finding composition of all clusters - ctrl c to cancel') -% %[clusters,strs,clusvec,all_bas,ba_counts] = cluster_ba(clusters,1:length(clusters)); -% %disp('Success - printing table.') -% %diary on -% -% catch -% end -% end - -% try to get ICBM composition for all clusters -% stricbm = icbm_orthview_label(clusters); -% for i = 1:length(clusters) -% clusters(i).ICBMstr = stricbm{i}; -% end - -% new for Carmack labels, do L3 and L5 (most informative levels). -dolabs = 1; %dolabs = input('Do text labels for clusters (current = Carmack labels)?'); - -if dolabs && ~(exist('talairach_info.mat') == 2) - disp('Cannot find talairach_info.mat, so cannot get Talairach labels.'); - dolabs = 0; -end - -if dolabs - fprintf(1,'Loading database.'); - if isempty(xyz), load talairach_info xyz, end - if isempty(L3), load talairach_info L3, end - if isempty(L5), load talairach_info L5, end - - fprintf(1,'Done. Getting text labels.'); - fprintf(1,'%03d',0); - for i = 1:length(clusters) - fprintf(1,'\b\b\b%03d',i); - [name,perc,number,totalnum,stricbm{i}] = Carmack_get_label(clusters(i).XYZmm,L3,xyz); - [name,perc,number,totalnum,stricbm2{i}] = Carmack_get_label(clusters(i).XYZmm,L5,xyz); - end - - fprintf(1,'Done.\n'); -end - -for i = 1:length(clusters) - - if isfield(clusters,'correl'), - if ~isempty(clusters(i).correl) - try - cmatx(i,1) = clusters(i).correl;, - catch - cmatx(i,1) = NaN; - end - else - cmatx(i,1) = NaN; - end - else cmatx(i,1) = NaN; - end - - cmatx(i,2) = clusters(i).numVox; - maxZ = clusters(i).Z(abs(clusters(i).Z(1,:)) == max(abs(clusters(i).Z(1,:)))); - cmatx(i,3) = maxZ(1); - - % not working yet - %clusters(i).cor_stat = tor_r2z(a(1,2),length(clusters(i).timeseries-1); - - % for use with tor_get_spheres2.m, breaking up cluster into spheres - % OLD - %if isfield(clusters,'center') & exist('M') == 1 & isfield(clusters,'from_cluster') - % cmatx(i,4) = clusters(i).from_cluster; - % centers{i} = (round(voxel2mm(clusters(i).XYZ(:,clusters(i).Z == max(clusters(i).Z)),M)')); - % cmatx(i,5) = centers{i}(1); - % cmatx(i,6) = centers{i}(2); - % cmatx(i,7) = centers{i}(3); - %end - - % ----------------------------------------------------------------------------------- - % Define variables to report in an easy-to-access matrix (cmatx) - % ----------------------------------------------------------------------------------- - - if isfield(clusters,'corr_range'), - cmatx(i,8) = clusters(i).corr_range(1); - cmatx(i,9) = clusters(i).corr_range(end); - end - - if isfield(clusters,'snr_avgts'), - cmatx(i,10) = clusters(i).snr_avgts; - end - - if isfield(clusters,'snr'), - cmatx(i,11) = min(clusters(i).snr); - cmatx(i,12) = max(clusters(i).snr); - end - - if isfield(clusters,'numpos') & isfield(clusters,'power80'), - cmatx(i,13) = clusters(i).numpos; - cmatx(i,14) = ceil(clusters(i).power80); - end - - if isfield(clusters,'numpeaks'), cmatx(i,15) = clusters(i).numpeaks;, end - - x(i) = clusters(i).mm_center(1); - y(i) = clusters(i).mm_center(2); - z(i) = clusters(i).mm_center(3); - - if exist('v') == 1 - vox = mm2voxel(clusters(i).mm_center,V); - cmatx(i,16) = round(v(vox(1),vox(2),vox(3))); - end -end - - % ----------------------------------------------------------------------------------- - % Print table header - % ----------------------------------------------------------------------------------- - - disp(' ') - if isempty(clusters), disp('No clusters.'), return, end - if isfield(clusters,'name'),disp(clusters(1).name),end - -if isfield(clusters,'center') & exist('M') == 1 & isfield(clusters,'from_cluster') - % sort by which cluster its from - try,cmatx = sortrows(cmatx,4);,catch,end - fprintf(1,'corr\tvoxels\tmaxZ\tfrom_clust\tmax_coords\n') - for i = 1:size(cmatx,1) - fprintf(1,'%3.2f\t%3.0f\t%3.2f\t%3.0f\t%3.0f\t%3.0f\t%3.0f\t\n',cmatx(i,1),cmatx(i,2),cmatx(i,3),cmatx(i,4),cmatx(i,5),cmatx(i,6),cmatx(i,7)) - end -else - disp(' ') - if isfield(clusters,'shorttitle'),fprintf(1,'Name\t'),end - fprintf(1,'index\tx\ty\tz\tcorr\tvoxels\tvolume_mm3\tmaxZ\t') - if isfield(clusters,'numpeaks'),fprintf(1,'numpeaks\t'), end - if isfield(clusters,'corr_range'), fprintf(1,'mincorr\tmaxcorr\t'), end - if isfield(clusters,'snr_avgts'),fprintf(1,'snr_avgts(d)\t'), end - if isfield(clusters,'snr'),fprintf(1,'minsnr\tmaxsnr\t'), end - if isfield(clusters,'numpos') & isfield(clusters,'power80'), - fprintf(1,'numpos\tpower80\t') - end - if exist('v') == 1 - fprintf(1,('BA\tBA_composition\t')) - end - if exist('stricbm') == 1 - %fprintf(1,('ICBM_single_subj\t')) - fprintf(1,('Carmack_Tal_Labels\t')) - end - - if exist('stricbm2') == 1 - %fprintf(1,('ICBM_single_subj\t')) - fprintf(1,('Carmack_Level5\t')) - end - fprintf(1,'\n') - - % ----------------------------------------------------------------------------------- - % Print a row for each cluster - % ----------------------------------------------------------------------------------- - - for i = 1:size(cmatx,1) - if isfield(clusters,'shorttitle'),fprintf(1,'%s\t',clusters(i).shorttitle);,end - fprintf(1,'%3.0f\t%3.0f\t%3.0f\t%3.0f\t%3.2f\t%3.0f\t%3.0f\t%3.5f\t',i,x(i),y(i),z(i),cmatx(i,1),cmatx(i,2),cmatx(i,2).*prod(clusters(i).voxSize),cmatx(i,3)) - if isfield(clusters,'numpeaks'),fprintf(1,'%3.0f\t',cmatx(i,15)), end - if isfield(clusters,'corr_range'), fprintf(1,'%3.2f\t%3.2f\t',cmatx(i,8),cmatx(i,9)), end - if isfield(clusters,'snr_avgts'), fprintf(1,'%3.2f\t',cmatx(i,10)), end - if isfield(clusters,'snr'), fprintf(1,'%3.2f\t%3.2f\t',cmatx(i,11),cmatx(i,12)), end - if isfield(clusters,'numpos') & isfield(clusters,'power80'), - fprintf(1,'%3.0f\t%3.0f\t',cmatx(i,13),cmatx(i,14)) - end - if exist('v') == 1 - fprintf(1,('%3.0f\t'),cmatx(i,16)) - end - - if exist('strs') == 1 - fprintf(1,'%s\t',strs(i,:)) - end - - if exist('stricbm') == 1 - fprintf(1,('%s\t'),stricbm{i}) - end - - if exist('stricbm2') == 1 - fprintf(1,('%s\t'),stricbm2{i}) - end - - fprintf(1,'\n') - - if length(varargin) > 0 - % print sub-cluster table - whsc = cat(1,subc.from_cluster) == i; - for j = find(whsc)' - print_row(subc(j),j,clusters) - end - end - - end -end - -return - - - - - -function print_row(clusters,i,bigcl) -% prints a row for a subcluster (varargin{1}) below its corresponding cluster - - if isfield(clusters,'correl'), cmatx(i,1) = clusters(1).correl;, - else cmatx(i,1) = NaN; - end - - cmatx(i,2) = clusters(1).numVox; - cmatx(i,3) = max(clusters(1).Z); - - if isfield(clusters,'corr_range'), - cmatx(i,8) = clusters(1).corr_range(1); - cmatx(i,9) = clusters(1).corr_range(end); - elseif isfield(bigcl,'corr_range'), - cmatx(i,8) = NaN; - cmatx(i,9) = NaN; - end - - if isfield(clusters,'snr_avgts'), - cmatx(i,10) = clusters(1).snr_avgts; - elseif isfield(bigcl,'snr_avgts'), - cmatx(i,10) = NaN; - cmatx(i,10) = NaN; - end - - if isfield(clusters,'snr'), - cmatx(i,11) = min(clusters(1).snr); - cmatx(i,12) = max(clusters(1).snr); - elseif isfield(bigcl,'snr'), - cmatx(i,11) = NaN; - cmatx(i,12) = NaN; - end - - if isfield(clusters,'numpos') & isfield(clusters,'power80'), - cmatx(i,13) = clusters(1).numpos; - cmatx(i,14) = ceil(clusters(1).power80); - elseif isfield(bigcl,'numpos') & isfield(bigcl,'power80'), - cmatx(i,13) = NaN; - cmatx(i,14) = NaN; - end - - %if isfield(clusters,'numpeaks'), - % cmatx(i,15) = clusters(1).numpeaks;, - %elseif isfield(bigcl,'numpeaks'), - % cmatx(i,15) = NaN; - %end - -fprintf(1,'%s\t%3.0f\t%3.0f\t%3.0f\t%3.2f\t%3.0f\t%3.5f\t', ... - '->',clusters(1).mm_center(1),clusters(1).mm_center(2),clusters(1).mm_center(3), ... - cmatx(i,1),cmatx(i,2),cmatx(i,3)) - - %if isfield(clusters,'numpeaks'),fprintf(1,'%3.0f\t',cmatx(i,15)), end - fprintf('\t') % skip numpeaks - makes no sense for subcluster - if isfield(bigcl,'corr_range'), fprintf(1,'%3.2f\t%3.2f\t',cmatx(i,8),cmatx(i,9)), end - if isfield(bigcl,'snr_avgts'), fprintf(1,'%3.2f\t',cmatx(i,10)), end - if isfield(bigcl,'snr'), fprintf(1,'%3.2f\t%3.2f\t',cmatx(i,11),cmatx(i,12)), end - if isfield(bigcl,'numpos') & isfield(bigcl,'power80'), - fprintf(1,'%3.0f\t%3.0f\t',cmatx(i,13),cmatx(i,14)) - end - - fprintf(1,'\n') - -return diff --git a/CanlabCore/Cluster_contig_region_tools/clusters2mask.m b/CanlabCore/Cluster_contig_region_tools/clusters2mask.m index 075e5e29..dcf20ada 100644 --- a/CanlabCore/Cluster_contig_region_tools/clusters2mask.m +++ b/CanlabCore/Cluster_contig_region_tools/clusters2mask.m @@ -75,22 +75,19 @@ end switch spm('Ver') - case 'SPM2' - % spm_defaults is a script - disp('WARNING: spm defaults not set for spm2. Make sure your defaults are set correctly'); - - case 'SPM5' - % spm_defaults is a function - - if(isempty(defaults)) - spm_defaults(); - end - - if ~isfield(V, 'dt') - warning('Using default datatype; Enter dt field in input structure to use yours.'); - V.dt(1) = spm_type('float32'); - V.dt(2) = 1; - end + case 'SPM2' + % SPM2: spm_defaults is a script, not callable here + disp('WARNING: spm defaults not set for spm2. Make sure your defaults are set correctly'); + otherwise + % SPM5+, including any future versions + if isempty(defaults) + spm_defaults(); + end + if ~isfield(V, 'dt') + warning('Using default datatype; Enter dt field in input structure to use yours.'); + V.dt(1) = spm_type('float32'); + V.dt(2) = 1; + end end V.fname = fname; diff --git a/CanlabCore/Data_extraction/extract_from_rois.m b/CanlabCore/Data_extraction/extract_from_rois.m index aace004d..754881e4 100644 --- a/CanlabCore/Data_extraction/extract_from_rois.m +++ b/CanlabCore/Data_extraction/extract_from_rois.m @@ -99,23 +99,12 @@ %% clear imgdat switch spm('Ver') - - - case {'SPM12', 'SPM8', 'SPM5'} - - - imgdat = iimg_get_data(volInfo, imgs_to_extract_from, 'single', 'verbose', 'noexpand'); - - case {'SPM2', 'SPM99'} - % legacy, for old SPM - - + % legacy SPM imgdat = iimg_get_data(volInfo, imgs_to_extract_from, 'single', 'verbose'); - - otherwise - error('Unknown version of SPM! Update code, check path, etc.'); + % SPM5+, including any future versions + imgdat = iimg_get_data(volInfo, imgs_to_extract_from, 'single', 'verbose', 'noexpand'); end diff --git a/CanlabCore/Data_extraction/extract_image_data.m b/CanlabCore/Data_extraction/extract_image_data.m index 078ff3a3..1ded8c7f 100644 --- a/CanlabCore/Data_extraction/extract_image_data.m +++ b/CanlabCore/Data_extraction/extract_image_data.m @@ -96,23 +96,12 @@ %% clear imgdat switch spm('Ver') - - - case {'SPM12', 'SPM8', 'SPM5'} - - - imgdat = iimg_get_data(volInfo, imgs_to_extract_from, 'single', 'verbose', 'noexpand'); - - case {'SPM2', 'SPM99'} - % legacy, for old SPM - - + % legacy SPM imgdat = iimg_get_data(volInfo, imgs_to_extract_from, 'single', 'verbose'); - - otherwise - error('Unknown version of SPM! Update code, check path, etc.'); + % SPM5+, including any future versions + imgdat = iimg_get_data(volInfo, imgs_to_extract_from, 'single', 'verbose', 'noexpand'); end diff --git a/CanlabCore/Data_extraction/extract_raw_data.m b/CanlabCore/Data_extraction/extract_raw_data.m index 2e798d59..d1a2e8a4 100644 --- a/CanlabCore/Data_extraction/extract_raw_data.m +++ b/CanlabCore/Data_extraction/extract_raw_data.m @@ -193,12 +193,11 @@ switch spm('Ver') case 'SPM2' - % spm_defaults is a script + % SPM2: spm_defaults is a script, not callable here disp('WARNING: spm defaults not set for spm2. Make sure your defaults are set correctly'); - - case 'SPM5' - % spm_defaults is a function - if(isempty(defaults)) + otherwise + % SPM5+, including any future versions + if isempty(defaults) spm_defaults(); end end diff --git a/CanlabCore/Data_extraction/load_atlas.m b/CanlabCore/Data_extraction/load_atlas.m index 088617bc..498cc8cf 100644 --- a/CanlabCore/Data_extraction/load_atlas.m +++ b/CanlabCore/Data_extraction/load_atlas.m @@ -1,86 +1,257 @@ function atlas_obj = load_atlas(atlas_file_name_or_keyword, varargin) -% Load one of a collection of atlases by keyword -% -% atlas_obj = load_atlas(varargin) -% -% List of keywords/atlases available: -% ------------------------------------------------------------------------- -% 'canlab2024[_fine|_coarse][_fmriprep20|_fsl6][_1mm|_2mm] -% 'Combined atlas from other published, whole brain. Available in a fine or coarse (default) parcellation in -% MNI152NLin2009cAsym (aka fmriprep) space (default) or MNI152NLin6Asym (aka fsl) space in 1mm or 2mm (default) -% resolutions. Additional parcellations available with downsample_parcellation(). Refer to github README for -% details. Assembled dynamically when called due to distribution restrictions of Bianciardi subatlas. Development -% is ongoing (Date: 03/07/2024)' -% 'opencanlab2024[_fine|_coarse][_fmriprep20|_fsl6][_1mm|2mm_] -% 'Variation on canlab2024 that only uses regions with open usage and distribution licenses. No client side -% assembly needed, but has fewer brainstem areas, and some have synthetic probabilities. Used as the starting -% point for dynamic assembly of canlab2024 though and the two have much in common. Defauts = {coarse,fmriprep20,2mm} -% 'canlab2023[_fine|_coarse][_fmriprep20|_fsl6][_1mm|_2mm] -% 'Combined atlas from other published, whole brain. Available in a fine or coarse (default) parcellation in -% MNI152NLin2009cAsym (aka fmriprep) space (default) or MNI152NLin6Asym (aka fsl) space in 1mm or 2mm (default) -% resolutions. Additional parcellations available with downsample_parcellation(). Refer to github README for -% details. Development is frozen, so it may be more stable than CANLab2024 (Date: 3/07/2024)' -% 'canlab2018[_2mm]' 'Combined atlas from other published atlases, whole brain. (Deprecated in favor of canlab2023)' -% 'desikan_killiany[_fsl6|_fmriprep20]' -% 'Desikan-Killiany cortical gyral/sulcal labeling from Freesurfer (2006). Gyri include pial surface and lateral banks. Default: fmriprep20' -% 'dkt[_fsl6|_fmriprep20] 'Kline and Tourville's update to the desikan killiany atlas projected to fsl or fmriprep spaces. Default: fmriprep20' -% 'destrieux[_fsl6|_fmriprep20]' 'Destrieux cortical gyral/sulcal labeling from Freesurfer (2009). Gyral/sulcal division is based on curvature values. Default: fmriprep20' -% 'thalamus' 'Thalamus_combined_atlas_object.mat' -% 'thalamus_detail', 'morel[_fsl6|_fmriprep20]', -% 'Morel_thalamus_atlas_object.mat in MNI152NLin6Asym (fsl) space (default) or MNI152NLin2009cAsym (fmriprep) space. -% (Both in MasksPrivate)' -% 'iglesias_thal[_fmriprep20|_fsl6]' -% 'Iglesias/Freesurfer thalamic nuclear parcellation in fmriprep20 (default) or fsl6 space. A bit more coarse than morel, -% but open license, more accurate boundaries and probablistic' -% 'iglesias_hypothal[_fmriprep20|_fsl6] -% 'Billot/Iglesias/Freesurfer hypothalamic segmentation in fmriprep20 (default) or fsl6 space. -% 'cortex', 'glasser' -% 'Glasser 2016 multimodal cortical parcellation volumetric projection using nearest neighbor interpolation from -% surface (deprecated)' -% 'glasser_[fmriprep20|fsl6]' 'Glasser 2016 multimodal cortical parcellation volumetric projection using registration fusion to two surface -% templates using 2 studies (N=241/89) -% 'basal_ganglia', 'bg' 'Basal_ganglia_combined_atlas_object.mat' -% 'striatum', 'pauli_bg' 'Pauli2016_striatum_atlas_object.mat' -% 'brainstem' 'brainstem_combined_atlas_object.mat' -% 'subcortical_rl','cit168' 'CIT168 MNI152Nlin2009cAsym subcortical atlas v1.0.0 (deprecated)' -% 'cit168_[fmriprep20|fsl6]' 'CIT168 v1.1.0 subcortical atlas in fmriprep20 or fsl6 space' -% 'cit168_amygdala_[fmriprep20|fsl6] -% 'CIT168 v1.0.3 amygdalar nuclear parcellation in fmriprep20 or fsl6 space' -% 'brainnetome' 'Brainnetome_atlas_object.mat' -% 'keuken' 'Keuken_7T_atlas_object.mat' -% 'buckner' 'buckner_networks_atlas_object.mat' -% 'cerebellum[_fsl6|_fmriprep20]', 'suit[_fsl6|_fmriprep20]' -% 'Diedrichsen cerrebellar atlas in MNI152NLin6Asym space (aka fsl, default) or MNI152NLin2009cAsym space (aka fmriprep).' -% 'shen[_fmriprep20|_fsl6]' 'Shen_atlas_object.mat, in MNIColin27v1998 space (default), MNI152NLin6Asym (fsl) and MNI152NLin2009cAsym (fmriprep 20.2.3) spaces' -% 'schaefer400' *Not saved as object yet* 'Schaefer2018Cortex_atlas_regions.mat' -% 'yeo17networks' 'Schaefer2018Cortex_17networks_atlas_object.mat' -% 'insula' 'Faillenot_insular_atlas.mat' -% 'painpathways' 'pain_pathways_atlas_obj.mat' -% 'painpathways_finegrained' 'pain_pathways_atlas_obj.mat' -% 'painpathways2024' 'pain_pathways2024_atlas_obj.mat' -% 'painpathways2024_finegrained' 'pain_pathways2024_atlas_obj.mat' -% 'tian_3t_[fmriprep20|fsl6]' -% 'Subcortical atlas at four different resolutions and two different reference spaces. Use atlas/get_coarser_parcellation to select low resolution versions.' -% 'delavega' 'delaVega2017_neurosynth_atlas_object' -% 'julich_[fmriprep20|fsl6]' 'Histological Julich Brain atlas in fmriprep 20.2.3 LTS (default) or fsl spaces' -% 'bianciardi[_fmriprep20|_fsl6][_2mm] -% 'Bianciardi brainstem atlas in fmriprep 20.2.3 LTS space (default) or fsl spaces at 1mm (default) or 2mm sampling resolution' -% 'cartmell_NAc[_fmriprep20|_fsl6] 'NAc Core/Shell probablistic atlas' -% 'harvard_aan[_fmriprep20|_fsl6] 'Harvard ascending arousal network atlas version 2.0. A generalization of the reticular activating system to various other brainstem nuclei besides the -% midbrain reticular formation. Based on histology, immunihistochemistry, and DWI tractography.' -% 'limbic_brainstem_atlas[_fmriprep20|fsl_6] -% 'Levinson Bari Limbic Brainstem Atlas. Includes VTA, dorsal raphe, locus coereleus, nucleus tractus solitaris and PAG. Probablistic with an open usage license.' -% -% More information and references to original publications are saved in -% each atlas object. This function is a shell to collect them in a central registry. -% New atlases can be created by passing a file name (e.g., .nii file) or an fmri_data object -% and labels into the atlas( ) constructor method. -% -% Examples: -% ------------------------------------------------------------------------- -% atlas_obj = load_atlas('thalamus'); -% atlas_obj = load_atlas('Thalamus_atlas_combined_Morel.mat');morel +% load_atlas Load a CANlab atlas object by keyword or filename. % +% :Usage: +% :: +% +% atlas_obj = load_atlas(atlas_file_name_or_keyword, [optional inputs]) +% +% This function is a central registry that resolves a keyword (e.g., +% 'canlab2024', 'thalamus', 'glasser_fsl6') to a saved +% atlas-class object on the MATLAB path and returns it. If a string +% that is not a recognized keyword is passed, it is treated as a filename +% and resolved with which(). More information and references to the +% original publications are stored inside each atlas object. +% +% Some atlases are dynamically assembled the first time they are loaded +% (e.g., canlab2023, canlab2024, bianciardi); subsequent calls reload +% the cached .mat file and rebuild only when an updated build is +% available. New atlases can be created by passing a file name (e.g., a +% .nii file) or an fmri_data object together with a list of labels +% into the atlas() constructor method. +% +% :Inputs: +% +% **atlas_file_name_or_keyword:** +% A character/string keyword (see :Available Keywords: below) +% identifying a registered atlas, or a filename for a saved +% atlas object. Keywords are matched case-insensitively. +% Unrecognized strings are passed to which() and treated as +% filenames. +% +% :Optional Inputs: +% +% **'verbose':** +% Print progress messages while loading. Default: on. +% +% **'noverbose':** +% Suppress progress messages. +% +% :Outputs: +% +% **atlas_obj:** +% An atlas-class object containing the requested parcellation +% with .dat (region indices), .probability_maps, +% .labels, .label_descriptions, and .references +% populated. Returns [] if the requested file cannot be +% located on the MATLAB path. +% +% :Available Keywords: +% +% The list below maps each keyword (or family of keywords with optional +% suffixes in square brackets) to the underlying atlas it loads. +% Suffixes _fmriprep20 / _fsl6 select the reference space +% (MNI152NLin2009cAsym vs. MNI152NLin6Asym); _1mm / _2mm select +% sampling resolution; _fine / _coarse select parcellation +% granularity, where applicable. Defaults are noted per atlas. +% +% :: +% +% 'canlab2024[_fine|_coarse][_fmriprep20|_fsl6][_1mm|_2mm]' +% Combined atlas from other published, whole-brain atlases. +% Available in a fine or coarse (default) parcellation in +% MNI152NLin2009cAsym (aka fmriprep) space (default) or +% MNI152NLin6Asym (aka fsl) space in 1mm or 2mm (default) +% resolutions. Additional parcellations available with +% downsample_parcellation(). Refer to github README for details. +% Assembled dynamically when called due to distribution +% restrictions of Bianciardi subatlas. Development is ongoing +% (Date: 03/07/2024). +% +% 'opencanlab2024[_fine|_coarse][_fmriprep20|_fsl6][_1mm|_2mm]' +% Variation on canlab2024 that only uses regions with open usage +% and distribution licenses. No client-side assembly needed, but +% has fewer brainstem areas, and some have synthetic +% probabilities. Used as the starting point for dynamic assembly +% of canlab2024 though and the two have much in common. Defaults +% = {coarse, fmriprep20, 2mm}. +% +% 'canlab2023[_fine|_coarse][_fmriprep20|_fsl6][_1mm|_2mm]' +% Combined atlas from other published, whole-brain atlases. +% Available in a fine or coarse (default) parcellation in +% MNI152NLin2009cAsym (aka fmriprep) space (default) or +% MNI152NLin6Asym (aka fsl) space in 1mm or 2mm (default) +% resolutions. Additional parcellations available with +% downsample_parcellation(). Refer to github README for details. +% Development is frozen, so it may be more stable than +% CANLab2024 (Date: 3/07/2024). +% +% 'canlab2018[_2mm]' +% Combined atlas from other published atlases, whole brain. +% (Deprecated in favor of canlab2023.) +% +% 'desikan_killiany[_fsl6|_fmriprep20]' +% Desikan-Killiany cortical gyral/sulcal labeling from +% Freesurfer (2006). Gyri include pial surface and lateral +% banks. Default: fmriprep20. +% +% 'dkt[_fsl6|_fmriprep20]' +% Kline and Tourville's update to the Desikan-Killiany atlas +% projected to fsl or fmriprep spaces. Default: fmriprep20. +% +% 'destrieux[_fsl6|_fmriprep20]' +% Destrieux cortical gyral/sulcal labeling from Freesurfer +% (2009). Gyral/sulcal division is based on curvature values. +% Default: fmriprep20. +% +% 'thalamus' +% Thalamus_combined_atlas_object.mat. +% +% 'thalamus_detail', 'morel[_fsl6|_fmriprep20]' +% Morel_thalamus_atlas_object.mat in MNI152NLin6Asym (fsl) space +% (default) or MNI152NLin2009cAsym (fmriprep) space. (Both in +% MasksPrivate.) +% +% 'iglesias_thal[_fmriprep20|_fsl6]' +% Iglesias/Freesurfer thalamic nuclear parcellation in +% fmriprep20 (default) or fsl6 space. A bit more coarse than +% morel, but open license, more accurate boundaries and +% probabilistic. +% +% 'iglesias_hypothal[_fmriprep20|_fsl6]' +% Billot/Iglesias/Freesurfer hypothalamic segmentation in +% fmriprep20 (default) or fsl6 space. +% +% 'cortex', 'glasser' +% Glasser 2016 multimodal cortical parcellation volumetric +% projection using nearest neighbor interpolation from surface +% (deprecated). +% +% 'glasser_[fmriprep20|fsl6]' +% Glasser 2016 multimodal cortical parcellation volumetric +% projection using registration fusion to two surface templates +% using 2 studies (N=241/89). +% +% 'basal_ganglia', 'bg' +% Basal_ganglia_combined_atlas_object.mat. +% +% 'striatum', 'pauli_bg' +% Pauli2016_striatum_atlas_object.mat. +% +% 'brainstem' +% brainstem_combined_atlas_object.mat. +% +% 'subcortical_rl', 'cit168' +% CIT168 MNI152Nlin2009cAsym subcortical atlas v1.0.0 +% (deprecated). +% +% 'cit168_[fmriprep20|fsl6]' +% CIT168 v1.1.0 subcortical atlas in fmriprep20 or fsl6 space. +% +% 'cit168_amygdala_[fmriprep20|fsl6]' +% CIT168 v1.0.3 amygdalar nuclear parcellation in fmriprep20 or +% fsl6 space. +% +% 'brainnetome' +% Brainnetome_atlas_object.mat. +% +% 'keuken' +% Keuken_7T_atlas_object.mat. +% +% 'buckner' +% buckner_networks_atlas_object.mat. +% +% 'cerebellum[_fsl6|_fmriprep20]', 'suit[_fsl6|_fmriprep20]' +% Diedrichsen cerebellar atlas in MNI152NLin6Asym space (aka +% fsl, default) or MNI152NLin2009cAsym space (aka fmriprep). +% +% 'shen[_fmriprep20|_fsl6]' +% Shen_atlas_object.mat, in MNIColin27v1998 space (default), +% MNI152NLin6Asym (fsl) and MNI152NLin2009cAsym (fmriprep 20.2.3) +% spaces. +% +% 'schaefer400' +% *Not saved as object yet.* Schaefer2018Cortex_atlas_regions.mat. +% +% 'yeo17networks' +% Schaefer2018Cortex_17networks_atlas_object.mat. +% +% 'insula' +% Faillenot_insular_atlas.mat. +% +% 'painpathways' +% pain_pathways_atlas_obj.mat. +% +% 'painpathways_finegrained' +% pain_pathways_atlas_obj.mat (fine-grained variant). +% +% 'painpathways2024' +% pain_pathways2024_atlas_obj.mat. +% +% 'painpathways2024_finegrained' +% pain_pathways2024_atlas_obj.mat (fine-grained variant). +% +% 'tian_3t_[fmriprep20|fsl6]' +% Subcortical atlas at four different resolutions and two +% different reference spaces. Use atlas/get_coarser_parcellation +% to select low-resolution versions. +% +% 'delavega' +% delaVega2017_neurosynth_atlas_object. +% +% 'julich_[fmriprep20|fsl6]' +% Histological Julich Brain atlas in fmriprep 20.2.3 LTS +% (default) or fsl spaces. +% +% 'bianciardi[_fmriprep20|_fsl6][_2mm]' +% Bianciardi brainstem atlas in fmriprep 20.2.3 LTS space +% (default) or fsl spaces at 1mm (default) or 2mm sampling +% resolution. +% +% 'cartmell_NAc[_fmriprep20|_fsl6]' +% NAc Core/Shell probabilistic atlas. +% +% 'harvard_aan[_fmriprep20|_fsl6]' +% Harvard ascending arousal network atlas version 2.0. A +% generalization of the reticular activating system to various +% other brainstem nuclei besides the midbrain reticular +% formation. Based on histology, immunohistochemistry, and DWI +% tractography. +% +% 'limbic_brainstem_atlas[_fmriprep20|_fsl6]' +% Levinson Bari Limbic Brainstem Atlas. Includes VTA, dorsal +% raphe, locus coeruleus, nucleus tractus solitarius, and PAG. +% Probabilistic with an open usage license. +% +% :Examples: +% :: +% +% % Load a registered atlas by keyword +% atlas_obj = load_atlas('thalamus'); +% +% % Load by filename (passed through which()) +% atlas_obj = load_atlas('Thalamus_atlas_combined_Morel.mat'); +% +% % Load the default canlab2024 (coarse, fmriprep20, 2mm) +% atlas_obj = load_atlas('canlab2024'); +% +% % Load a specific variant explicitly, suppressing progress messages +% atlas_obj = load_atlas('glasser_fsl6', 'noverbose'); +% +% :See also: +% - atlas (constructor for the returned object class) +% - load_image_set (analogous keyword-based loader for fmri_data) +% - downsample_parcellation +% +% .. +% Programmers' notes: +% Most atlases are stored as `atlas`-class .mat files distributed via +% the canlab/Neuroimaging_Pattern_Masks repo. A handful are assembled +% on demand via creation scripts (create_CANLab2023_atlas, +% create_CANLab2024_atlas, bianciardi_create_atlas_obj); for those, +% the loader compares a stored hash to a `.latest` sentinel and +% rebuilds when the cached object is out of date. +% .. % docustom = 0; diff --git a/CanlabCore/Data_extraction/load_image_set.m b/CanlabCore/Data_extraction/load_image_set.m index 2d964f0e..53df01c9 100644 --- a/CanlabCore/Data_extraction/load_image_set.m +++ b/CanlabCore/Data_extraction/load_image_set.m @@ -1,15 +1,26 @@ function [image_obj, networknames, imagenames] = load_image_set(image_names_or_keyword, varargin) -% Locate a series of images on the path and load them into an fmri_data -% object. Useful for loading sets of canonical masks or patterns. +% load_image_set Locate a series of images on the path and load them into an fmri_data object. % -% - Checks whether images exist on path -% - Returns full image names with path names -% - Returns formatted networknames for plot labels -% -% Usage: +% :Usage: % :: % -% [imgs, names] = load_image_set(image_names_or_keyword) +% [image_obj, networknames, imagenames] = load_image_set(image_names_or_keyword, [optional inputs]) +% +% Useful for loading sets of canonical masks or predictive 'signature' +% patterns. The function: +% +% - Checks whether images exist on the MATLAB path. +% - Returns full image names with path names. +% - Returns formatted networknames for plot labels. +% +% This function is a central registry that resolves a keyword (e.g., +% 'emotionreg', 'npsplus', 'kragel18') to a known image set on the +% MATLAB path and returns the loaded images. If a string that is not a +% recognized keyword is passed, it is treated as a list of filenames and +% loaded directly. Some image sets are stored in the CANlab +% Neuroimaging_Pattern_Masks repository, some in MasksPrivate, and other +% (unlisted) datasets can be loaded if you have a load_.m file +% in your path (this is intended for extensions in other libraries). % % .. % Author and copyright information: @@ -33,219 +44,274 @@ % :Inputs: % % **image_names_or_keyword:** -% A string matrix with images to load, or a keyword. -% keywords load pre-defined image sets, as indicated below. -% NOTE: you will need to have these images on your Matlab path! -% Some are in the CANlab Neuroimaging_Pattern_Masks repository, -% some in Masks_Private repository, other (unlisted) datasets can be -% loaded if you have a load_.m file in your path. This is -% for simplified extensions to this method by other libraries. -% -% Sample test datasets - one image per subject -% ------------------------------------------------------------------------ -% 'list' : List of signatures (enter any keyword from list as input) -% Note: returns table as 1st output instead of image_obj -% -% Sample test datasets - one image per subject -% ------------------------------------------------------------------------ -% 'emotionreg' : N = 30 emotion regulation sample dataset from Wager et al. 2008. -% Each image is a contrast image for the contrast [reappraise negative vs. look negative] -% -% 'bmrk3', 'pain' : 33 participants, with brain responses to six levels of heat (non-painful and painful). -% NOTE: requires access to bmrk3_6levels_pain_dataset.mat, -% on figshare (see canlab.github.io/walkthroughs) -% -% 'kragel18_alldata' : 270 subject maps from Kragel 2018; -% These are saved in kragel_2018_nat_neurosci_270_subjects_test_images.mat -% if not found, will attempt to download from Neurovault using -% retrieve_neurovault_collection(). -% -% Sample test datasets - one image per trial (single trial datasets) -% ------------------------------------------------------------------------ -% A set of single-trial datasets for pain studies have been compiled by Bogdan Petre and stored here: -% https://github.com/canlab/canlab_single_trials -% -% Each dataset has a name (e.g., 'nsf', 'exp', 'bmrk3pain'), and you can enter -% any of these names as keywords, or 'all_single_trials' to load all of -% them. The canlab_single_trials repo must be on your matlab path. -% Each study file loads as an fmri_data object, with a metadata_table field -% that stores trial info in a Matlab table-class object. -% -% Single-trial datasets include: -% % 'nsf' 'bmrk3pain' 'bmrk3warm' 'bmrk4' 'exp' 'ie' 'ie2' 'ilcp' -% 'romantic' 'scebl' 'stephan' -% -% Each contains a single-trial object from a differnt study. -% -% Parcellations and large-scale networks/patterns -% ------------------------------------------------------------------------ -% 'bucknerlab': 7 network parcellation from Yeo et al., cortex only -% -% 'bucknerlab_wholebrain': 7 networks in cortex, BG, cerebellum -% -% 'bucknerlab_wholebrain_plus': 7 networks in cortex, BG, cerebellum + SPM Anatomy Toolbox regions + brainstem -% -% 'allengenetics': Five maps from the Allen Brain Project human gene expression maps -% from Luke Chang (unpublished) -% -% 'bgloops', 'pauli' : 5-basal ganglia parcels and 5 associated cortical -% networks from Pauli et al. 2016 -% -% 'bgloops17', 'pauli17' : 17-parcel striatal regions only from Pauli et al. 2016 -% -% 'bgloops_cortex' : Cortical regions most closely associated with -% the Pauli 5-region striatal clusters -% -% 'pet_nr_map', 'hansen22' 'pet' 'receptorbinding' : 2022_Hansen_PET_tracer_maps, 30 maps with -% different combinations of tracers and neurotransmitter receptors -% -% 'emometa' 'emotionmeta' '2015emotionmeta' : 2015 Wager/Kang et al. -% Meta-analysis maps for 5 basic categories. -% 'Anger' 'Disgust' 'Fear' 'Happy' 'Sad' -% -% 'marg' or 'transmodal' or 'principalgradient': 2016 PNAS Margulies -% MNI152NLin2009cAsym_margulies_grad1.nii.gz -% 'margfsl' : MNI152NLin6Asym_margulies_grad1.nii.gz -% -% 'transcriptomic_gradients': -% Principal transcriptomic gradients from -% Hawrylycz et al. An anatomically comprehensive atlas of the adult human brain transcriptome. (2012) Nature -% Vogel et al. "Deciphering the functional specialization of whole-brain spatiomolecular gradients in the adult brain" (2024) PNAS -% [transcriptomic_grads transcriptomic_names] = load_image_set('transcriptomic_gradients'); -% -% 'Signature' patterns and predictive models -% ------------------------------------------------------------------------ -% 'list' : Return list of signatures in a table -% 'all' : Load all signatures in table registry (see 'list') -% 'nps' : Wager et al. 2013 Neurologic Pain Signature -% 'vps' : Krishnan et et al. 2016 Vicarious Pain Signature -% 'rejection': Woo et et al. 2014 Romantic Rejection -% 'siips' : Woo et et al. 2017 Stimulus intensity-independent pain Signature -% 'pines' : Chang et et al. 2015 Picture-induced negative emotion Signature -% 'gsr' : Eisenbarth et al. 2016 Stress-induced skin conductance -% 'hr' : Eisenbarth et al. 2016 Stress-induced heart rate -% 'multisensory' : Lopez-sola et al. 2016 Fibromyalgia multisensory pattern -% 'fmpain' : Lopez-sola et al. 2016 Fibromyalgia pain-period pattern -% 'plspain' : Kragel el al. 2018 PLS pain-related -% 'cpdm' : Geuter et al. 2020 multivariate mediation pain-related +% A character/string keyword (see :Available Keywords: below) +% identifying a registered image set, a string matrix or cell +% array of image filenames to load, or an existing fmri_data / +% atlas object (in which case names are extracted from the input). +% NOTE: you will need to have the requested images on your MATLAB +% path! Some are in the CANlab Neuroimaging_Pattern_Masks +% repository, some in MasksPrivate, and other (unlisted) datasets +% can be loaded if you have a load_.m file in your path. +% +% :Optional Inputs: +% +% **'noverbose':** +% Suppress printing of all loaded image names. Default is to print +% all image names. +% +% **'verbose':** +% Print loaded image names (default). +% +% **'md5check':** +% Perform md5 hash check if supported for the requested dataset. +% If verbosity is enabled, md5 check results will be returned to +% stdout. (Only honored by custom load_.m functions in +% other extension repositories.) +% +% **'forcedl':** +% Force download without prompting for permission if dataset is +% missing. (Only honored by custom load_.m functions in +% other extension repositories.) % -% 'npsplus' : Wager lab published multivariate patterns: -% NPS (incl NPSpos & NPSpos), SIIPS, PINES, Romantic Rejection, VPS, more -% -% 'painsig' : NPS (incl NPSpos & NPSpos) and SIIPS only -% -% 'fibromyalgia': patterns used to predict FM from Lopez Sola et al.: -% NPSp, FM-pain, FM-multisensory -% -% 'guilt' : a multivariate fMRI pattern related to guilt behavior -% Yu, Koban et al. 2019, Cerebral Cortex -% Yu_guilt_SVM_sxpo_sxpx_EmotionForwardmask.nii.gz +% :Outputs: % -% 'neurosynth', 'neurosynth_featureset1': 525 "Reverse inference" z-score maps from Tal Yarkoni's -% Neurosynth, unthresholded, 2013 +% **image_obj:** +% fmri_data object with the maps loaded. % -% 'neurosynth_topics_forwardinference' 'neurosynth_topics_reverseinference' -% 54 topic maps from Yarkoni & Poldrack 2014 topic modeling analysis -% Selected from 100 topics for psychological relevance -% and given ChatGPT-based summary topic labels by Ke et al. 2024, Nat Neurosci +% **networknames:** +% Cell array of names based on the image names or custom titles. % -% 'pain_cog_emo', 'kragel18': Partial least squares maps for generalizable -% representations of pain, cog control, emotion. From -% Kragel et al. 2018, Nature Neuroscience +% **imagenames:** +% Cell array of names of images loaded. % -% 'pain_pdm', 'pdm': High-dimensional mediators of pain. 10 -% individual PDM maps and a combined PDM, which is a weighted -% combination of the 10. From Geuter et al. (2020) Cerebral Cortex -% (see cpdm above) +% :Available Keywords: % -% 'kragelemotion': 7 emotion-predictive models from Kragel & LaBar 2015 +% The list below maps each keyword (or family of keywords) to the +% underlying image set it loads. Keywords are matched case-sensitively. +% Unrecognized strings are treated as filenames. % -% 'kragelschemas': 20 visual emotion-schemas from Kragel et al. 2019 +% :: % -% {'reddanCSplus' 'threat'}: Reddan et al. 2018 Neuron CS+ vs. CS- classifier map -% 'zhouvps': Zhou et al. 2020 eLife generalized vicarious pain signature +% Sample test datasets - one image per subject +% -------------------------------------------------------------------- +% 'list' : List of signatures (enter any keyword from list as input) +% Note: returns table as 1st output instead of image_obj % -% 'multiaversive', 'mpa2': Ceko et al. multiple predictive patterns -% for aversive experience: General, Mechanical pain, -% Aversive Sounds, Thermal pain, Visual aversive images +% 'emotionreg' : N = 30 emotion regulation sample dataset from Wager et al. 2008. +% Each image is a contrast image for the contrast +% [reappraise negative vs. look negative]. +% +% 'bmrk3', 'pain' : 33 participants, with brain responses to six levels of heat +% (non-painful and painful). +% NOTE: requires access to bmrk3_6levels_pain_dataset.mat, +% on figshare (see canlab.github.io/walkthroughs). +% +% 'kragel18_alldata' : 270 subject maps from Kragel 2018; +% These are saved in +% kragel_2018_nat_neurosci_270_subjects_test_images.mat. +% If not found, will attempt to download from +% Neurovault using +% retrieve_neurovault_collection(). +% +% Sample test datasets - one image per trial (single trial datasets) +% -------------------------------------------------------------------- +% A set of single-trial datasets for pain studies have been compiled +% by Bogdan Petre and stored here: +% https://github.com/canlab/canlab_single_trials % -% 'stroop': Silvestrini et al. 2020 Stroop-demand SVM. stroop_pattern_wani_121416.nii +% Each dataset has a name (e.g., 'nsf', 'exp', 'bmrk3pain'), and you +% can enter any of these names as keywords, or 'all_single_trials' to +% load all of them. The canlab_single_trials repo must be on your +% MATLAB path. Each study file loads as an fmri_data object, with a +% metadata_table field that stores trial info in a MATLAB table-class +% object. % -% 'ncs': drug and food craving signature(s) -% Koban et al, Nature Neuroscience 2022 +% Single-trial datasets include: +% 'nsf' 'bmrk3pain' 'bmrk3warm' 'bmrk4' 'exp' 'ie' 'ie2' 'ilcp' +% 'romantic' 'scebl' 'stephan' +% +% Each contains a single-trial object from a different study. +% +% Parcellations and large-scale networks/patterns +% -------------------------------------------------------------------- +% 'bucknerlab' : 7 network parcellation from Yeo et al., cortex only. +% +% 'bucknerlab_wholebrain' : 7 networks in cortex, BG, cerebellum. +% +% 'bucknerlab_wholebrain_plus' : 7 networks in cortex, BG, +% cerebellum + SPM Anatomy Toolbox +% regions + brainstem. +% +% 'allengenetics' : Five maps from the Allen Brain Project human +% gene expression maps from Luke Chang +% (unpublished). +% +% 'bgloops', 'pauli' : 5 basal ganglia parcels and 5 associated +% cortical networks from Pauli et al. 2016. +% +% 'bgloops17', 'pauli17' : 17-parcel striatal regions only from +% Pauli et al. 2016. +% +% 'bgloops_cortex' : Cortical regions most closely associated with +% the Pauli 5-region striatal clusters. +% +% 'pet_nr_map', 'hansen22', 'pet', 'receptorbinding' : +% 2022_Hansen_PET_tracer_maps, 30 maps with +% different combinations of tracers and +% neurotransmitter receptors. +% +% 'emometa', 'emotionmeta', '2015emotionmeta' : +% 2015 Wager/Kang et al. Meta-analysis maps for +% 5 basic categories: 'Anger' 'Disgust' 'Fear' +% 'Happy' 'Sad'. +% +% 'marg' or 'transmodal' or 'principalgradient' : +% 2016 PNAS Margulies +% MNI152NLin2009cAsym_margulies_grad1.nii.gz. +% +% 'margfsl' : MNI152NLin6Asym_margulies_grad1.nii.gz. +% +% 'transcriptomic_gradients' : +% Principal transcriptomic gradients from +% Hawrylycz et al. An anatomically comprehensive +% atlas of the adult human brain transcriptome. +% (2012) Nature; and Vogel et al. "Deciphering +% the functional specialization of whole-brain +% spatiomolecular gradients in the adult brain" +% (2024) PNAS. +% Example: +% [transcriptomic_grads transcriptomic_names] = ... +% load_image_set('transcriptomic_gradients'); +% +% 'Signature' patterns and predictive models +% -------------------------------------------------------------------- +% 'list' : Return list of signatures in a table. +% 'all' : Load all signatures in table registry (see 'list'). +% 'nps' : Wager et al. 2013 Neurologic Pain Signature. +% 'vps' : Krishnan et al. 2016 Vicarious Pain Signature. +% 'rejection': Woo et al. 2014 Romantic Rejection. +% 'siips' : Woo et al. 2017 Stimulus intensity-independent pain +% Signature. +% 'pines' : Chang et al. 2015 Picture-induced negative emotion +% Signature. +% 'gsr' : Eisenbarth et al. 2016 Stress-induced skin +% conductance. +% 'hr' : Eisenbarth et al. 2016 Stress-induced heart rate. +% 'multisensory' : Lopez-sola et al. 2016 Fibromyalgia multisensory +% pattern. +% 'fmpain' : Lopez-sola et al. 2016 Fibromyalgia pain-period +% pattern. +% 'plspain' : Kragel et al. 2018 PLS pain-related. +% 'cpdm' : Geuter et al. 2020 multivariate mediation pain-related. +% +% 'npsplus' : Wager lab published multivariate patterns: +% NPS (incl NPSpos & NPSneg), SIIPS, PINES, Romantic +% Rejection, VPS, more. +% +% 'painsig' : NPS (incl NPSpos & NPSneg) and SIIPS only. +% +% 'fibromyalgia' : Patterns used to predict FM from Lopez Sola et al.: +% NPSp, FM-pain, FM-multisensory. +% +% 'guilt' : A multivariate fMRI pattern related to guilt behavior. +% Yu, Koban et al. 2019, Cerebral Cortex. +% Yu_guilt_SVM_sxpo_sxpx_EmotionForwardmask.nii.gz. +% +% 'neurosynth', 'neurosynth_featureset1' : +% 525 'Reverse inference' z-score maps from Tal +% Yarkoni's Neurosynth, unthresholded, 2013. +% +% 'neurosynth_topics_forwardinference', +% 'neurosynth_topics_reverseinference' : +% 54 topic maps from Yarkoni & Poldrack 2014 topic +% modeling analysis. Selected from 100 topics for +% psychological relevance and given ChatGPT-based +% summary topic labels by Ke et al. 2024, +% Nat Neurosci. +% +% 'pain_cog_emo', 'kragel18' : +% Partial least squares maps for generalizable +% representations of pain, cog control, emotion. From +% Kragel et al. 2018, Nature Neuroscience. +% +% 'pain_pdm', 'pdm' : +% High-dimensional mediators of pain. 10 individual +% PDM maps and a combined PDM, which is a weighted +% combination of the 10. From Geuter et al. (2020) +% Cerebral Cortex (see cpdm above). +% +% 'kragelemotion' : 7 emotion-predictive models from +% Kragel & LaBar 2015. +% +% 'kragelschemas' : 20 visual emotion-schemas from Kragel et al. +% 2019. +% +% {'reddanCSplus' 'threat'} : Reddan et al. 2018 Neuron CS+ vs. CS- +% classifier map. +% +% 'zhouvps' : Zhou et al. 2020 eLife generalized vicarious pain +% signature. +% +% 'multiaversive', 'mpa2' : +% Ceko et al. multiple predictive patterns for aversive +% experience: General, Mechanical pain, Aversive +% Sounds, Thermal pain, Visual aversive images. +% +% 'stroop' : Silvestrini et al. 2020 Stroop-demand SVM. +% stroop_pattern_wani_121416.nii. +% +% 'ncs' : Drug and food craving signature(s). +% Koban et al, Nature Neuroscience 2022. % craving_wmapN99_boot10K_02-May-2022.img % wmap_onlyDRUGS_l2nGM_N99_20220428.img % wmap_onlyFOOD_l2nGM_N99_20220428.img % -% 'pifonem': Murillo .. Ashar J Pain 2026 - Picture Induced Fear of -% Neck Movement (PiFoneM). In ppl with acute and chronic -% whiplash, predicts fear of neck movements -% -% -% :Optional inputs: -% -% **noverbose:** -% Suppress printing of all loaded image names. Default is to print -% all image names. -% -% **md5check:** -% Perform md5 hash check if supported for dataset. If verbosity is -% enabled md5 check results will be returned to stdout. -% -% **forcedl:** -% Force download without prompting for permission if dataset is -% missing. -% -% :Outputs: -% -% **image_obj:** -% fmri_data object with the maps loaded -% -% **networknames:** -% cell array of names based on the image names or custom titles -% -% **imagenames:** -% cell array of names of images loaded +% 'pifonem' : Murillo .. Ashar J Pain 2026 - Picture Induced Fear +% of Neck Movement (PiFoneM). In ppl with acute and +% chronic whiplash, predicts fear of neck movements. % % :Examples: % :: % -% % Example 1: Load NPS (private) and several other signatures -% % ------------------------------------------------------------------------- -% imagenames = {'weights_NSF_grouppred_cvpcr.img' ... % NPS -% 'Rating_Weights_LOSO_2.nii' ... % PINES -% 'dpsp_rejection_vs_others_weights_final.nii' ... % rejection -% 'bmrk4_VPS_unthresholded.nii'}; -% -% [obj, netnames, imgnames] = load_image_set(imagenames); -% -% The above loads a subset of the same images as: -% -% [obj, netnames, imgnames] = load_image_set('npsplus'); -% -% % Example 2: Apply the PLS signatures from Kragel et al. 2018 to the emotion regulation dataset -% % ------------------------------------------------------------------------- -% % Load PLS signatures from Kragel et al. 2018 -% [obj, names] = load_image_set('pain_cog_emo'); -% bpls_wholebrain = get_wh_image(obj, [8 16 24]); -% names_wholebrain = names([8 16 24]); -% bpls_subregions = get_wh_image(obj, [1:6 9:14 17:22]); -% names_subregions = names([1:6 9:14 17:22]); -% -% % Load test data: Emotion regulation from Wager et al. 2008 -% test_data_obj = load_image_set('emotionreg'); -% -% % Make plots -% % Yellow: positive associations. Blue: Negative associations. Plot shows mean +- std. error for each pattern of interest -% -% create_figure('Kragel Pain-Cog-Emo maps', 1, 2); -% stats = image_similarity_plot(test_data_obj, 'average', 'mapset', bpls_wholebrain, 'networknames', names_wholebrain, 'nofigure'); -% subplot(1, 2, 2) -% stats = image_similarity_plot(test_data_obj, 'average', 'mapset', bpls_subregions, 'networknames', names_subregions, 'nofigure'); +% % Example 1: Load NPS (private) and several other signatures +% % ---------------------------------------------------------------- +% imagenames = {'weights_NSF_grouppred_cvpcr.img' ... % NPS +% 'Rating_Weights_LOSO_2.nii' ... % PINES +% 'dpsp_rejection_vs_others_weights_final.nii' ... % rejection +% 'bmrk4_VPS_unthresholded.nii'}; +% +% [obj, netnames, imgnames] = load_image_set(imagenames); +% +% % The above loads a subset of the same images as: +% [obj, netnames, imgnames] = load_image_set('npsplus'); +% +% % Example 2: Apply the PLS signatures from Kragel et al. 2018 to +% % the emotion regulation dataset +% % ---------------------------------------------------------------- +% % Load PLS signatures from Kragel et al. 2018 +% [obj, names] = load_image_set('pain_cog_emo'); +% bpls_wholebrain = get_wh_image(obj, [8 16 24]); +% names_wholebrain = names([8 16 24]); +% bpls_subregions = get_wh_image(obj, [1:6 9:14 17:22]); +% names_subregions = names([1:6 9:14 17:22]); +% +% % Load test data: Emotion regulation from Wager et al. 2008 +% test_data_obj = load_image_set('emotionreg'); +% +% % Make plots +% % Yellow: positive associations. Blue: Negative associations. +% % Plot shows mean +- std. error for each pattern of interest +% create_figure('Kragel Pain-Cog-Emo maps', 1, 2); +% stats = image_similarity_plot(test_data_obj, 'average', 'mapset', ... +% bpls_wholebrain, 'networknames', names_wholebrain, 'nofigure'); +% subplot(1, 2, 2) +% stats = image_similarity_plot(test_data_obj, 'average', 'mapset', ... +% bpls_subregions, 'networknames', names_subregions, 'nofigure'); % % :See also: -% -% image_similarity_plot, fmri_data +% - image_similarity_plot +% - fmri_data +% - load_atlas % .. % Programmers' notes: diff --git a/CanlabCore/Data_extraction/timeseries_extract_slice.m b/CanlabCore/Data_extraction/timeseries_extract_slice.m index 0cc4d652..ed88864e 100644 --- a/CanlabCore/Data_extraction/timeseries_extract_slice.m +++ b/CanlabCore/Data_extraction/timeseries_extract_slice.m @@ -14,16 +14,10 @@ % defaults switch spm('Ver') case 'SPM2' - % spm_defaults is a script + % SPM2: spm_defaults is a script, not callable here disp('WARNING: spm defaults not set for spm2. Make sure your defaults are set correctly'); - - case {'SPM5', 'SPM8', 'SPM12'} - % spm_defaults is a function - spm_defaults() - otherwise - % unknown SPM - disp('Unknown version of SPM!'); + % SPM5+, including any future versions spm_defaults() end diff --git a/CanlabCore/Data_processing_tools/canlab_extract_ventricle_wm_timeseries.m b/CanlabCore/Data_processing_tools/canlab_extract_ventricle_wm_timeseries.m index bd0726c7..77ef59c5 100644 --- a/CanlabCore/Data_processing_tools/canlab_extract_ventricle_wm_timeseries.m +++ b/CanlabCore/Data_processing_tools/canlab_extract_ventricle_wm_timeseries.m @@ -1,30 +1,70 @@ function [vw_nuisance, vw_nuisance_comps] = canlab_extract_ventricle_wm_timeseries(mask_image_dir, imgs, varargin) - -% function [nuisance, nuisance_comps] = canlab_extract_ventricle_wm_timeseries(mask_image_dir) +% canlab_extract_ventricle_wm_timeseries Extract ventricle and white-matter nuisance time series for fMRI. +% +% :Usage: +% :: +% +% [vw_nuisance, vw_nuisance_comps] = canlab_extract_ventricle_wm_timeseries(mask_image_dir, imgs, ['noplot']) +% +% Extracts data from ventricles and white matter for each time point in +% a functional time series. You can include vw_nuisance in your +% nuisance matrix in first-level model estimation, and/or use the PCA +% components in vw_nuisance_comps (e.g., as in aCompCor). +% +% 5/4/2012 by Tor Wager and Wani Woo. +% +% :Inputs: +% +% **mask_image_dir:** +% Directory containing mask files for ventricle and white +% matter. You can use canlab_create_wm_ventricle_masks.m to +% produce these mask files. The mask file names must be +% ventricles.img and white_matter.img. Example: +% mask_image_dir = fullfile(subjdir, 'Structural/SPGR'); +% +% **imgs:** +% Image file names that you want to extract data from. Example: +% imgs = filenames(fullfile(subjdir, ... +% 'Functional/Preprocessed/run1/wra*nii'), 'absolute', 'char'); +% +% :Optional Inputs: +% +% **'noplot':** +% Suppress the diagnostic plot. The default behavior is to plot +% the output variables. +% +% :Outputs: % -% This function extracts data from ventricles and white matter for each -% time series. You can include vw_nuisance in your nuisance matrix in the -% first level model estimation. +% **vw_nuisance:** +% T x 2 matrix of MEAN ventricle and white-matter signal. +% Column 1: ventricle signal. Column 2: white-matter signal. +% Rows correspond to time points in the input time series. % -% output: "vw_nuisance" returns the MEAN ventricle and white matter signal -% 1st column: ventricle signal, 2nd column: white matter signal, -% rows: time series +% **vw_nuisance_comps:** +% T x 10 matrix containing the first 5 PCA components of the +% ventricle voxel time series followed by the first 5 PCA +% components of the white-matter voxel time series. % -% "vw_nuisance_comps" returns first 5 components from PCA. +% :Examples: +% :: % -% input: -% mask_image_dir: a directory that has mask files for ventricle and white matter. -% You can use "canlab_create_wm_ventricle_masks.m" to get these mask files. -% The mask file names must be "ventricles.img' and 'white_matter.img'. -% e.g) mask_image_dir = fullfile(subjdir, 'Structural/SPGR'); +% mask_image_dir = fullfile(subjdir, 'Structural/SPGR'); +% imgs = filenames(fullfile(subjdir, ... +% 'Functional/Preprocessed/run1/wra*nii'), 'absolute', 'char'); % -% imgs: image file names that you want to extract data from. -% e.g) imgs = filenames(fullfile(subjdir, 'Functional/Preprocessed/run1/wra*nii'), 'absolute', 'char'); +% % With diagnostic plot +% [vw_nuisance, vw_nuisance_comps] = ... +% canlab_extract_ventricle_wm_timeseries(mask_image_dir, imgs); % -% option (varargin): 'noplot' - the default setting is plotting output -% variables. You can use 'noplot' option if you don't want the plot. +% % Without plot +% [vw_nuisance, vw_nuisance_comps] = ... +% canlab_extract_ventricle_wm_timeseries(mask_image_dir, imgs, 'noplot'); % -% 5/4/2012 by Tor Wager and Wani Woo +% :See also: +% - canlab_create_wm_ventricle_masks +% - fmri_data +% - filenames +% - pca %% -------------------------------------------- % Extract data from ventricles diff --git a/CanlabCore/External/spider/basic/@data/data.asv b/CanlabCore/External/spider/basic/@data/data.asv deleted file mode 100755 index d25b1c6f..00000000 --- a/CanlabCore/External/spider/basic/@data/data.asv +++ /dev/null @@ -1,74 +0,0 @@ - -function data = data(name,X,Y) - -%======================================================================================================== -% DATA data object -%======================================================================================================== -% Stores data into two components X (input) and Y (output). -% -% An object is created with: -% data('name',X,Y); -% or -% data(X,Y); (no name) -% or -% data(X); (in case there's no output, e.g clustering) -% -% -% Public attributes: -% X -- input matrix -% Y -- output matrix -% index -- original parent indices of examples -% findex -- original parent indices of features -% -% Public methods: -% d=get(data,index,featInd) -- return data,index only (ind)examples,(find)features -% x=get_x(data,index,fInd) -- return x,index only (ind)examples,(find)features -% y=get_y(data,index,fInd) -- return y,index only (ind)examples,(find)features -% d=set_x(data,X,indes,featInd) -- set inputs indexed by (ind)examples,(find)features -% d=set_y(data,Y,index,featInd) -- set outputs indxed by (ind)examples,(find)features -% [indes,featInd]=get_index(d) -- returns example and feature indices -% [numEx,vDim,oDim,numCls]=get_dim(d) -- returns number of examples,features,output dimensions,classes -% -% Import methods: -% Data can import libsvm or arff files. Note that arff does not encode -% what is input or output thus the resulting data object has an empty Y member. -% Nominal attributes are ma -% Furthremore, missing attributes are indicated by "NaN". -% -% Example: -% get_x(data([1:5;6:10;11:15]),[1 3],[3:5]) -% dlibsvm=readfrom(data,'libsvm','mylibsvmtrainingfile'); -% darff=readfrom(data,'arff','mywekafile.arff'); - -% - if nargin==0 - name='data (empty)'; - data.X=[]; data.Y=[]; - data.index=[]; data.findex=[]; - %data = class(data,'data'); - else - data.X=[]; data.Y=[]; - if nargin>=2 data.X=X; end - if nargin>=3 data.Y=Y; end; - if ~isa(name,'char') - data.Y=data.X; - data.X=name; - name=['data']; - end - - X=data.X; - if isa(X,'cell') - data.index = [1:length(X)]; - data.findex = [1:length(X{1})]; - else - data.index = [1:size(X,1)]; - if isempty(data.index) data.index = [1:size(data.Y,1)]; end; - data.findex = [1:size(X,2)]; - end - - end - - p=algorithm(name); p.is_data=1; - data= class(data,'data',p); - - diff --git a/CanlabCore/External/spider/basic/@kernel/anisotropic_rbf.asv b/CanlabCore/External/spider/basic/@kernel/anisotropic_rbf.asv deleted file mode 100755 index 8d084c80..00000000 --- a/CanlabCore/External/spider/basic/@kernel/anisotropic_rbf.asv +++ /dev/null @@ -1,32 +0,0 @@ -function K = anisotropic_rbf(kern,dat1,dat2,ind1,ind2,kerParam), - -% K = rbf(d1,d2,ind1,ind2,param), compute the kernel -% matrix between d1 and d2 -% for a rbf kernel exp(-||x-z||^2/(2*param^2)) -% where x is from d1 and z from d2 - -w = []; -for i = 1:length(kerParam)-1 - w = [w,kerParam{i}]; -end -v = kerParam{end}; - -disp(['Training anisotropic RBF kernel with w = [' num2str(w) '] and v = [' num2str(v) ']']) - -X2 = get_x(dat2,ind2); -X1 = get_x(dat1,ind1); - -for i =1:size(X1,1) - for j = 1:size(X2,2) - s = sum(w.*(X1(i,:) - X2(j,:))) - K(i,j) = v*exp(-0.5 ); - end -end - - -% K=get_x(dat2,ind2)*get_x(dat1,ind1)'; -% kernTemp=kernel; -% Kdn = get_norm(kernTemp,dat1,ind1).^2; -% Kn = get_norm(kernTemp,dat2,ind2).^2; -% K = ones(length(Kn),1)*Kdn' + Kn*ones(1,length(Kdn)) - 2*K; -% K = exp(-K/(2*kerParam^2)); diff --git a/CanlabCore/External/spider/basic/@kernel/levenstein.asv b/CanlabCore/External/spider/basic/@kernel/levenstein.asv deleted file mode 100755 index b3ea9c2e..00000000 --- a/CanlabCore/External/spider/basic/@kernel/levenstein.asv +++ /dev/null @@ -1,35 +0,0 @@ -function K = levenstein(kern,dat1,dat2,ind1,ind2,kerParam) -%---calculation the distance matrix---- -D= []; -X1 = get_x(dat1); -X2 = get_x(dat2); -for i = ind1 - for j = ind2 - D(i,j) = d(X1{i},X2{j}); - end -end - -%---get kernel matrix from distance matrix-- -clear X1 X2 %mem -K = []; -for i = ind1 - for j = ind2 - D(i,j) = d(X1{i},X2{j}); - end -end - - -function s = d(a,b) - %----------initializing------------- - C = zeros(length(a)+1,length(b)+1); - C(:,1) = [0:length(a)]'; - C(1,:) = [0:length(b)]; - %------compute distance------------- - for i = 2:size(C,1) - for j = 2:size(C,2) - delta = 1-abs(sign(a(i-1)-b(j-1))); - C(i,j) = min([C(i-1,j)+1,C(i,j-1)+1,C(i-1,j-1) + 1 - delta]); - end - end - - s = C(end,end); \ No newline at end of file diff --git a/CanlabCore/External/spider/basic/@kernel/poly.asv b/CanlabCore/External/spider/basic/@kernel/poly.asv deleted file mode 100755 index 2d8e427a..00000000 --- a/CanlabCore/External/spider/basic/@kernel/poly.asv +++ /dev/null @@ -1,6 +0,0 @@ -function K = poly(kern,dat1,dat2,ind1,ind2,kerParam), - -% K = poly(d1,d2,ind1,ind2,param), compute the kernel matrix between d1 and d2 -% for a polynomial kernel (+1)^param where x is from d1 and z from d2 -X2 = get_x(dat2,ind2) -K=(*get_x(dat1,ind1)'+1).^kerParam; diff --git a/CanlabCore/External/spider/basic/@kernel/symbol_string.asv b/CanlabCore/External/spider/basic/@kernel/symbol_string.asv deleted file mode 100755 index d999dadc..00000000 --- a/CanlabCore/External/spider/basic/@kernel/symbol_string.asv +++ /dev/null @@ -1,20 +0,0 @@ -function K = symbol_string(kern,dat1,dat2,ind1,ind2,kerParam) -% --- conversion to sequences------------------ -D= []; Xf = get_x(dat1); Xs = get_x(dat2); X1 = {}; X2 = {}; -for i = 1:size(Xf,1), tmp = Xf(i,:); X1{i} = tmp(tmp > 0); end -for i = 1:size(Xs,1), tmp = Xs(i,:); X2{i} = tmp(tmp > 0);end - -n = kernParam{1}; -lambda = kerParam{2}; - - - -%---------------------------------------------------------- -function ret = k_prime(s,t,lambda,n) - if n == 0 - ret = 1; return; - elseif min(length(s),length(t)) < n - - end - - \ No newline at end of file diff --git a/CanlabCore/External/spider/basic/@kernel/triangle_levenstein.asv b/CanlabCore/External/spider/basic/@kernel/triangle_levenstein.asv deleted file mode 100755 index e6deaa96..00000000 --- a/CanlabCore/External/spider/basic/@kernel/triangle_levenstein.asv +++ /dev/null @@ -1,52 +0,0 @@ -function K = squared_levenstein(kern,dat1,dat2,ind1,ind2,kerParam) -%---calculating the distance matrix---- -D= []; -Xf = get_x(dat1); -Xs = get_x(dat2); -X1 = {}; -X2 = {}; -for i = 1:size(X1,1) - tmp = Xf(i,:); - X1{i} = tmp(tmp > 0); -end -for i = 1:size(Xs,1) - tmp = Xs(i,:); - X2{i} = tmp(tmp > 0); -end - -% get -for i = 1:length(ind1) - disp(['D row ' num2str(i)]) - for j = 1:length(ind2) - D(i,j) = d(X1{i},X2{j}); - end -end -%---get kernel matrix from distance matrix-- -K = []; -m = size(D); - -for i = 1:length(ind1) - disp(['D->K row ' num2str(i)]) - for j = 1:length(ind2) - si = length(X1{i}); - sj = length(X2{j}); - p = (si + sj + D(i,j))/2; - K(i,j) = cos(2*atan(sqrt(((p-si)*(p-sj))/(p*(p-D(i,j)))))); - end -end - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -function s = d(a,b) - %----------initializing------------- - C = zeros(length(a)+1,length(b)+1); - C(:,1) = [0:length(a)]'; - C(1,:) = [0:length(b)]; - %------compute distance------------- - for i = 2:size(C,1) - for j = 2:size(C,2) - delta = 1-abs(sign(a(i-1)-b(j-1))); - C(i,j) = min([C(i-1,j)+1,C(i,j-1)+1,C(i-1,j-1) + 1 - delta]); - end - end - - s = C(end,end); \ No newline at end of file diff --git a/CanlabCore/External/spider/basic/@spiral/generate.asv b/CanlabCore/External/spider/basic/@spiral/generate.asv deleted file mode 100755 index 58b9b26b..00000000 --- a/CanlabCore/External/spider/basic/@spiral/generate.asv +++ /dev/null @@ -1,38 +0,0 @@ -function d = generate(a) - - -x1=zeros(a.m,1); -y1=zeros(a.m,1); - -x2=zeros(a.m,1); -y2=zeros(a.m,1); - -maxim=2*pi*a.n; -step=maxim; -t=step:step:a.m*step; - -x1= t.*cos(t) ; -y1= t.*sin(t); -x2= (1+t).*cos(t) ; -y2= (1+t).*sin(t) ; - -label=ones(a.m,1); -k=1; -for i=length(x1)+1:1:2*a.m - j=1+floor(length(x1)*rand); - x1=[x1,x1(j)+step*rand]; - y1=[y1,y1(j)+step*rand]; - x2=[x2,x2(j)+step*rand]; - y2=[y2,y2(j)+step*rand]; - if(j>a.m) - label(k)= - k=k+1; -end - -%plot(x1,y1,'+'); hold; -%plot(x2,y2,'r+'); - -%d=data(['spiral_data' ' n=' num2str(a.n)],x,y); -%d=data([get_name(d) ' -> ' get_name(a) ' '] ,Yest,Yt); -d=data([get_name(a) ' '] ,[x1' y1';x2' y2'],[ ones(a.m,1);-1*ones(a.m,1);]); - \ No newline at end of file diff --git a/CanlabCore/External/spider/basic/@spiral/spiral.asv b/CanlabCore/External/spider/basic/@spiral/spiral.asv deleted file mode 100755 index 26b58e13..00000000 --- a/CanlabCore/External/spider/basic/@spiral/spiral.asv +++ /dev/null @@ -1,35 +0,0 @@ - -function a = spiral(hyper) - -% TOY SPIRAL data generation object -% -% A=spiral(H) returns a spiral toy data object initialized with hyperparameters H. -% -% This generates 2 spirals with 4*m points per spiral, where 1*m points are -% points on two perfect spiral and 3*m points are some additive noise on -% the first quarter. -% -% -% Hyperparameters, and their defaults -% n=1 -- a parameter :-) play to explore -% m=50 -- number of used points per winding -% -% Model -% -% Methods: -% generate,train,test -% Example : -% d=gen(spiral({'n=1','m=50'})); -% [r s0]=train(svm(kernel('rbf',1)),d) -% plot(s0,[ -20 20 -20 20]); - - a.m=50; - a.n=1; - - - p=algorithm('spiral'); - a= class(a,'spiral',p); - - if nargin==1 - eval_hyper; - end diff --git a/CanlabCore/External/spider/functions/make_html_contents.asv b/CanlabCore/External/spider/functions/make_html_contents.asv deleted file mode 100755 index 73d1b79d..00000000 --- a/CanlabCore/External/spider/functions/make_html_contents.asv +++ /dev/null @@ -1,108 +0,0 @@ -function []=make_html_contents -disp('note that we assume to be in a directoy called spider'); -'going..' - - ss=spider_path; - -f=fopen('core_algorithms.txt','rt'); -fseek(f,0,'eof'); -l=ftell(f); -fseek(f,0,'bof'); -core_algos=char(fread(f,l)'); -fclose(f); - - - fin = fopen([ss 'Contents.m'],'r') - F = fread(fin); - s = char(F'); - fclose(fin); - -% fid = fopen(['c:\spider/doc/web/objects.html'],'w') - fid = fopen(['objects.html'],'w') - fprintf(fid,'\n'); - fprintf(fid,'\n'); - fprintf(fid,'\n'); - a='\n'; fprintf(fid,a); -a='\n'; fprintf(fid,a); -a='\n'; fprintf(fid,a); -a='\n'; fprintf(fid,a); -a='\n'; fprintf(fid,a); -a='
\n'; fprintf(fid,a); -a=' \n'; fprintf(fid,a); -a=' SPIDER \n'; fprintf(fid,a); -a=' \n'; fprintf(fid,a); -a='\n'; fprintf(fid,a); -a=' The Spider Objects\n'; fprintf(fid,a); -a=' \n'; fprintf(fid,a); -a='\n'; fprintf(fid,a); -a='
\n'; fprintf(fid,a); -a=' \n'; fprintf(fid,a); -a='
\n'; fprintf(fid,a); -a='
\n'; fprintf(fid,a); -a='

\n'; fprintf(fid,a); -a='\n'; fprintf(fid,a); -a=' \n'; fprintf(fid,a); -a='\n'; fprintf(fid,a); -a=' \n'; fprintf(fid,a); -a='

\n'; fprintf(fid,a);
-
-f=[find(s=='%')]; s(f)=' '; % purge comments
-s=s(find(s~=10)); 
-f=[find(double(s)==13)]; % find line feeds
-for i=2:length(f)-1
-   process(s(f(i):f(i+1)-1),fid,core_algos);
-end    
-a='
\n'; fprintf(fid,a); -fprintf(fid,'Objects marked with - are available in) -fclose(fid); - - -function a=process(s,fid,core_algos) -%------------------------ -a=[s(1:length(s)) ]; doit=1; - - -%a; keyboard - -%%------ search for title ---------- -if findstr('objects',a) & not(s(4)==' ') - a=['
' char(s) '
']; - fprintf(fid,'\n'); - fprintf(fid,'%s',a); - doit=0; -end -%%---------------------------------- - -if doit %% -----add font colors - f=min(find(a=='-')); - if not(isempty(f)) - a=s(2:f-1); a2=s(f+1:length(s)); - a=strtrim(a); - a2=strtrim(a2); - f1=min(find(not(a==' '))); - f2=max(find(not(a==' '))); - %f1=min(find((a>='a' & a<='z'))); - %f2=max(find((a>='a' & a<='z'))); - a_bef=a(1:f1-1); a_aft=a(f2+1:length(a)); - a=a(f1:f2); - obj_a=a; - - make_html_help(a); - a=[a_bef '' a ' ' a_aft ]; - s=['
' a ' ' ispartofcore(obj_a,core_algos) ' ' a2 char(13) '
']; - fprintf(fid,'%s',s); - end -% fprintf(fid,'
\n'); -end - -a=1; return; - - - -function s=ispartofcore(a,core_algos) - -if(isempty(strfind(core_algos,a))) - s='-'; -else - s='+'; -end \ No newline at end of file diff --git a/CanlabCore/External/spider/mclass/@one_vs_one/testing.asv b/CanlabCore/External/spider/mclass/@one_vs_one/testing.asv deleted file mode 100755 index 4484e1fc..00000000 --- a/CanlabCore/External/spider/mclass/@one_vs_one/testing.asv +++ /dev/null @@ -1,54 +0,0 @@ -function dat = testing(a,dat) - -%disp(['testing ' get_name(a) '.... ']) - -[numEx,vDim] = get_dim(dat); -oDim=a.nrofclasses; - - datTemp=dat; -sum((Ytmp==1),2) childIndex=0; - for i=1:oDim, - for j=(i+1):oDim, - childIndex=childIndex+1; - datTemp=set_name(datTemp,['Machine ' num2str(childIndex)]); - yEst(:,childIndex) = get_x(test(a.child{childIndex},datTemp)); - end; - end; -%% compute the score -childIndex=0; -score = zeros(numEx,oDim); -for i=1:oDim, - for j=(i+1):oDim, - childIndex=childIndex+1; - score(:,i) = score(:,i) + sign(yEst(:,childIndex)); - score(:,j) = score(:,j) - sign(yEst(:,childIndex)); - end; -end; -%% Take the max: -[dummy, ySort] = sort(-score,2); -yMax = ySort(:,1); -%% If there is some ties, we smooth the output of each classifier -%% by a hyperbolic tangeant and compute the real score for each classes -temp = find(dummy(:,1)==dummy(:,2)); -if ~isempty(temp), - childIndex=0; - score = zeros(length(temp),oDim); - for i=1:oDim, - for j=(i+1):oDim, - childIndex=childIndex+1; - score(:,i) = score(:,i) + tanh(yEst(temp,childIndex)); - score(:,j) = score(:,j) - tanh(yEst(temp,childIndex)); - end; - end; - [dummy, ySort] = sort(-score,2); - yMaxTemp = ySort(:,1); - yMax(temp) = yMaxTemp; -end; -yEst = -ones(numEx,oDim); - for i=1:length(yMax), - yEst(i,yMax(i))=1; - end; - -dat=set_x(dat,yEst); -dat=set_name(dat,[get_name(dat) ' -> ' get_name(a)]); - diff --git a/CanlabCore/External/spider/pat/@adaboost/adaboost.asv b/CanlabCore/External/spider/pat/@adaboost/adaboost.asv deleted file mode 100755 index 1595313c..00000000 --- a/CanlabCore/External/spider/pat/@adaboost/adaboost.asv +++ /dev/null @@ -1,56 +0,0 @@ - -function a = adaboost(C,hyper) - -% ADABOOST -% -% -% A=ADABOOST(H) implements basic boosting algorithm using max_margin linear hyperplanes. -% -% -% Hyperparameters, and their defaults -% kmax = 5 -- maximum number of weak learners -% -% Methods: -% train, test -% -% Model: -% child -- hyperplanes -% Example: -% -% % Use adaboost with 1-knn as weak learner and validate with 2 fold cross validation. -% c1=[2,0]; -% c2=[-2,0]; -% X1=randn(50,2)+repmat(c1,50,1); -% X2=randn(50,2)+repmat(c2,50,1); -% -% [r,a]=train(cv(adaboost(knn),'folds=2'),d); -% -% ======================================================================== -% Reference : The boosting approach to machine learning: An overview -% Author : Robert E. Schapire -% Link : http://www.cs.princeton.edu/~schapire/uncompress-papers.cgi/msri.ps -% ======================================================================== - - - a.kmax=5; - % model - if (nargin <1) - C=dualperceptron('margin=1'); - end - a.alpha=[]; - a.cjil - a.kmax=[]; - - p=algorithm('adaboost'); - a= class(a,'adaboost',p); - - if nargin==2, - eval_hyper; - end; - - a.alpha=ones(a.kmax,1); - for i=1:a.kmax - a.child{i}=C; - end - - disp([num2str(a.kmax),' (', C.name, ') classifiers ']) \ No newline at end of file diff --git a/CanlabCore/External/spider/pat/@knn/plot.asv b/CanlabCore/External/spider/pat/@knn/plot.asv deleted file mode 100755 index 96b6b6d8..00000000 --- a/CanlabCore/External/spider/pat/@knn/plot.asv +++ /dev/null @@ -1,112 +0,0 @@ -function plot(a,d) -% -% Usage : -% plot(a) -% -% a -- The algorithm already trained -% The colormap can be changed afterwards. -% -% Example: -% d=gen(spiral({'n=1','m=50'})); -% [r s0]=train(svm(kernel('rbf',1)),d) -% plot(s0); - - - -%clf -%d=a.keep; -x=get_x(d); -y=get_y(d); -ax1=min(x); ax2=max(x); - - -granul=100; -minX=floor(ax1(1)); -maxX=ceil(ax2(1)); -minY=floor(ax1(2)); -maxY=ceil(ax2(2)); -axis_sz=[minX maxX minY maxY]; -minX=minX-2; maxX=maxX+2; minY=minY-2; maxY=maxY+2; -gridx=[]; gridy=[]; - -mx=zeros(granul*granul,2); -for i=1:granul - for j=1:granul - mx((i-1)*granul+j,:)= [minX+(i-1)*(maxX-minX)/granul minY+(j-1)*(maxY-minY)/granul ] ; - gridx(i)=minX+(i-1)*(maxX-minX)/granul; - gridy(j)=minY+(j-1)*(maxY-minY)/granul; -end -end - - -temp=zeros(granul,granul); -dx=data(mx); -N=get_dim(dx); -blocks=N/50; -rr=[]; -last=1; - -% a.algorithm.use_signed_output=0; -%a.return_indices=1; -allx -for i=blocks:blocks:N - resX=test(a,get(dx,[last:i])); - rr=[rr;resX.X']; - last=i+1; -end - -for i=1:granul - for j=1:granul - temp(i,j)= rr( (i-1)*granul+j); - end -end -hold on; -%surf(1:granul,1:granul,temp'-1000); - -Ipos=find(rr(:,1)==+1); -Ineg=find(rr(:,1)==-1); - -plot(mx(Ipos,1),mx(Ipos,2),'r.'); -plot(mx(Ineg,1),mx(Ineg,2),'b.'); - - -%clf - -% colormap('cool') -% pcolor(gridx, gridy, temp') ; -% shading interp; - - -%surf(gridx,gridy,temp'); -%view(2); -%shading interp; -%[c,h]=contour(gridx,gridy,temp',[0 0],'k'); -% clabel(c,h); -colorbar - -if(1) - - Ipos=find(y==+1); - Ineg=find(y==-1); - h=plot(x(Ipos,1),x(Ipos,2),'rx'); hold on; - set(h,'LineWidth',2,'MarkerSize',5); - - h=plot(x(Ineg,1),x(Ineg,2),'ko'); hold on; - set(h,'LineWidth',2,'MarkerSize',5); -end - -axis(axis_sz); -% -% if( exist('dat')) -% for i=1:length(dat.X(:,1)) -% if dat.Y(i,1) > 0 -% plot3( granul+granul*(minX+dat.X(i,1))/(max_x-minX) ,granul+granul*(minY+dat.X(i,2))/(maxY-minY),-100,'resX'); -% else -% plot3( granul+granul*(minX+dat.X(i,1))/(max_x-minX) ,granul+granul*(minY+dat.X(i,2))/(maxY-minY),-100,'go'); -% end -% end -% end - - - - diff --git a/CanlabCore/External/spider/pat/@svm/plot.asv b/CanlabCore/External/spider/pat/@svm/plot.asv deleted file mode 100755 index f70b625b..00000000 --- a/CanlabCore/External/spider/pat/@svm/plot.asv +++ /dev/null @@ -1,161 +0,0 @@ -function data_square_plot(a,granul) -% -% Plots a surface plot of an svm which uses !_2_! dimensional inputs. -% Red decision line is drawn. -% -% Usage : -% plot(a) -% -% a -- The algorithm already trained -% The colormap can be changed afterwards. -% -% Example: -% d=gen(spiral({'n=1','m=50'})); -% [r s0]=train(svm(kernel('rbf',1)),d) -% plot(s0); - - -%clf -d=a.Xsv; -x=d.X; -y=d.Y; -ax1=min(x); ax2=max(x); -expand = 0.2 * [ax2-ax1]; -ax1 = ax1 - expand; -ax2 = ax2 + expand; - -c1=find(y==1); -c2=find(y==-1); - - - -granul=100; -minX=floor(ax1(1)); -maxX=ceil(ax2(1)); -minY=floor(ax1(2)); -maxY=ceil(ax2(2)); -axis_sz=[minX maxX minY maxY]; -minX=minX-2; maxX=maxX+2; minY=minY-2; maxY=maxY+2; -gridx=[]; gridy=[]; - -mx=zeros(granul*granul,2); -for i=1:granul - for j=1:granul - mx((i-1)*granul+j,:)= [minX+(i-1)*(maxX-minX)/granul minY+(j-1)*(maxY-minY)/granul ] ; - gridx(i)=minX+(i-1)*(maxX-minX)/granul; - gridy(j)=minY+(j-1)*(maxY-minY)/granul; - end -end - - -temp=zeros(granul,granul); - -sflag=a.algorithm.use_signed_output; -a.algorithm.use_signed_output=0; - -resX=test(a,data(mx)); -resX=sign(get_x(resX)).*sqrt(abs(get_x(resX))); - -for i=1:granul - for j=1:granul - temp(i,j)= resX( (i-1)*granul+j); - end -end -hold on; -%surf(1:granul,1:granul,temp'-1000); - - - -%clf -if 1 - % FeatureLines = [0 -1 1]'; %cheap hack to only get the decision boundary - colormap('gray') - % colormap('cool') - pcolor(gridx, gridy, temp') ; - [c,h] = contour(gridx, gridy, temp','k') ; - - if(length(h)>1) - set(h(1),'LineWidth',2); - end - - i=1; - while length(h)>i - set(h(i+1),'LineWidth',2); - i = i+1; - end - % FeatureLines=[-1 1]' ; % now the SV lines - [c,h] = contour(gridx, gridy, temp', 'c:') ; - if ~isempty(h) - set(h(1),'LineWidth',2,'LineStyle',':'); - i=1; - while length(h)>i - set(h(i+1),'LineWidth',2,'LineStyle',':'); - i = i+1; - end - end - shading interp -end - - -if 1 - pcolor(gridx,gridy,temp');colormap gray; - [c,h]=contour(gridx,gridy,temp',[-0.1:0.3:1],'k'); - i=1; - h=get(h,'Children'); - while length(h)>=i - set(h(i),'LineWidth',3); - i = i+1; - end - - if ~isempty(c) - h=clabel(c,h); - set(h,'FontSize',16) - end -else - mesh(gridx,gridy,temp');colormap gray; - view(3); -end - -shading interp; -colorbar - -if(1) - h=plot(x(c1,1),x(c1,2),'rx'); hold on; - set(h,'LineWidth',2,'MarkerSize',7); - h=plot(x(c2,1),x(c2,2),'bo'); - set(h,'LineWidth',2,'MarkerSize',7); -end - -sv=find(abs(a.alpha)>1e-7); - - -%sv=find(abs(a.alpha)>max(abs(a.alpha))/100); -amax=max(abs(a.alpha)); - -[r]=test(a,a.Xsv); - -if 1 - for i=sv' - col='co'; - if (r.X(i,:)*r.Y(i,:)<0.9) col='cs'; end; %% margin error - h=plot(x(i,1),x(i,2),col); - alpha=ceil((abs(a.alpha(i))/amax)*4); - set(h,'LineWidth',alpha,'MarkerSize',8+alpha); - end -end - -axis(axis_sz); -% -% if( exist('dat')) -% for i=1:length(dat.X(:,1)) -% if dat.Y(i,1) > 0 -% plot3( granul+granul*(minX+dat.X(i,1))/(max_x-minX) ,granul+granul*(minY+dat.X(i,2))/(maxY-minY),-100,'resX'); -% else -% plot3( granul+granul*(minX+dat.X(i,1))/(max_x-minX) ,granul+granul*(minY+dat.X(i,2))/(maxY-minY),-100,'go'); -% end -% end -% end - - - - diff --git a/CanlabCore/External/spider/pat/@svm/svm.asv b/CanlabCore/External/spider/pat/@svm/svm.asv deleted file mode 100755 index 4d9300cc..00000000 --- a/CanlabCore/External/spider/pat/@svm/svm.asv +++ /dev/null @@ -1,74 +0,0 @@ - -function a = svm(hyper) -%============================================================================= -% SVM Support Vector Machine object -%============================================================================= -% a=svm(hyperParam) -% -% Generates a svm object with given hyperparameters. -% -% -% Hyperparameters (with defaults) -% child=kernel -- the kernel is stored as a member called "child" -% C=Inf -- the soft margin C parameter -% ridge=1e-13 -- a ridge on the kernel -% balanced_ridge=0 -- for unbalanced data -% nu = 0 -- bernhard's nu svm parameter -% optimizer='default' -- other choices={andre,quadprog,svmlight, -% libsvm,svmtorch} -% alpha_cutoff=-1; -- keep alphas with abs(a_i)>alpha_cutoff -% default keeps all alphas, another -% reasonable choice is e.g alpha_cutoff=1e-5 to remove -% zero alphas (i.e non-SVs) to speed up computations. -% -% Model -% alpha -- the weights -% b0 -- the threshold -% Xsv -- the Support Vectors -% -% Note: -% For libsvm cache -% Methods: -% train, test, get_w -% -% Example: -% -% d=gen(spiral({'m=200','n=2','noise=0.35'})); -% [r,a]=train(cv(svm({kernel('rbf',1),'optimizer="andre"'})),d) -% plot(a{1}) -% -%============================================================================= -% Reference : A Tutorial on Support Vector Machines for Pattern Recognition -% Author : Christopher J. C. Burges -% Link : http://citeseer.ist.psu.edu/burges98tutorial.html -%============================================================================= - - %<<------hyperparam initialisation------------->> - a.child=kernel; - a.C=Inf; - a.ridge=1e-13; - a.balanced_ridge=0; - a.nu = 0; - a.optimizer='default'; - a.alpha_cutoff=-1; - - - % <<-------------model----------------->> - a.alpha=[]; - a.b0=0; - a.Xsv=[]; - a.nob=0; - - algoType=algorithm('svm'); - a= class(a,'svm',algoType); - - a.algorithm.alias={'kern','child'}; % kernel aliases - - if nargin==1, - eval_hyper; - end; - - - - - diff --git a/CanlabCore/External/spider/pat/@svm/training.asv b/CanlabCore/External/spider/pat/@svm/training.asv deleted file mode 100755 index a303d10d..00000000 --- a/CanlabCore/External/spider/pat/@svm/training.asv +++ /dev/null @@ -1,316 +0,0 @@ -function [retDat,algo] = training(algo,retDat) - -if algo.algorithm.verbosity>0 - disp(['training ' get_name(algo) '.... ']) -end - -opt=algo.optimizer; -if strcmp(algo.optimizer,'default') - len=length(retDat.Y); - if len<=200 %% <---if there are more then 200 eamples use svm_light - opt='andre'; - else - opt='libsvm'; - end -end -if algo.nu~=0, - opt='libsvm'; -end; - - -switch opt - case {'svmtorch'} - multi = 0; - regression = 0; - degree = 1; - gamma = 1; - eps = 0.001; - if strcmp(algo.child.ker,'linear') - kernelType = 0; - elseif strcmp(algo.child.ker,'poly') - kernelType = 1; - degree = algo.child.kerparam; - elseif strcmp(algo.child.ker,'rbf') - kernelType = 2; - gamma = algo.child.kerparam; - end; - [x y] = get_xy(retDat); - C = min(algo.C,10000); - [alpha, bias0, xSV] = SVMTorch(x,y,regression,multi,kernelType,degree,gamma,C,eps); - - %%<<----------------Andre optimizer-------------------->> - case {'andre'} - - [KerMa,algo.child]=train(algo.child,retDat); - KerMa=KerMa.X; %% calc kernel - KerMa=add_ridge(KerMa,algo,retDat); - y=get_y(retDat); - KerMa=KerMa.*(y*y'); - if( algo.nob == 0) - [alpha,bias] = quadsolve(KerMa,-ones(size(KerMa,1),1),y',0,algo.C); - bias0 = -bias; - else - alpha = quadsolve(KerMa,-ones(size(KerMa,1),1),[],0,algo.C); - end - - alpha= alpha .* y; - - - case {'andre_nob'} - - [KerMa,algo.child]=train(algo.child,retDat); - KerMa=KerMa.X; %% calc kernel - KerMa=add_ridge(KerMa,algo,retDat); - y=get_y(retDat); - KerMa=KerMa.*(y*y'); - alpha = quadsolve(KerMa,-ones(size(KerMa,1),1),[],0,algo.C); - bias0=0; - alpha= alpha .* y; - %%<<----------------quadprog optimizer-------------------->> - case {'quadprog'} - - [KerMa,algo.child]=train(algo.child,retDat); - KerMa=KerMa.X; %% <--- calculate the kernel - KerMa=add_ridge(KerMa,algo,retDat); - len=size(KerMa,1); - y=get_y(retDat); - KerMa=KerMa.*(y*y'); - opts= optimset('display','off','MaxIter',10000,'LargeScale','off'); - [alpha,fVAl,exit,out,lambda] = quadprog(KerMa,- ones(len,1),[],[],y',0, zeros(len,1),algo.C*ones(len,1),[],opts); - bias0=lambda.eqlin(1); - alpha= alpha .* y; - case {'quadprog_nob'} - - [KerMa,algo.child]=train(algo.child,retDat); - KerMa=KerMa.X; %% <--- calculate the kernel - KerMa=add_ridge(KerMa,algo,retDat); - len=size(KerMa,1); - y=get_y(retDat); - KerMa=KerMa.*(y*y'); - opts= optimset('display','off','MaxIter',10000,'LargeScale','off'); - [alpha,fVAl,exit,out] = quadprog(KerMa,-ones(len,1),[],[],[],[], zeros(len,1),algo.C*ones(len,1),[],opts); - bias0=0; - alpha= alpha .* y; - %%<<----------------svmlight optimizer-------------------->> - case {'svmlight'} - - algo.child.dat=retDat; %% <<-- kernel has to store data now - [x y]=get_xy(retDat); - if strcmp(algo.child.ker,'linear') - ker=1; - param1=1; - end; - if strcmp(algo.child.ker,'poly') - ker=2; - param1=algo.child.kerparam; - end; - if strcmp(algo.child.ker,'rbf') - ker=3; - param1=algo.child.kerparam; - param1=(2*param1^2); - end; - if strcmp(algo.child.ker,'weighted_linear') - ker=1; - param1=1; - tmp = algo.child.kerparam; - x=x .* repmat(tmp,size(x,1),1); %%<--- weight data according to parameters - end; - if strcmp(algo.child.ker,'weighted_rbf') - ker=4; - param1=1; - x=get_kernel(algo.child,retDat,retDat); %%<--- calculate kernel - x = [[1:size(x,1)]' x]; - end; - if strcmp(algo.child.ker,'custom_fast') | strcmp(algo.child.ker,'custom') | strcmp(algo.child.ker,'from_data') - x=get_kernel(algo.child,retDat,retDat); %%<--- calculate kernel - x = [[1:size(x,1)]' x]; - ker=4; - param1=1; - end; - x=full(x); - [alphas bias0 ind] = svmlight(x,y,algo.C,algo.ridge,algo.balanced_ridge,ker,param1, ... - max(0,algo.algorithm.verbosity-1)); - if algo.algorithm.verbosity>1 - disp('done!'); - end; - alpha=zeros(size(x,1),1); - alpha(ind+1)=alphas; - - case {'libsvm'} - - arglist={}; - x=[]; - y=[]; - svm_type=0; - kernelType=0; - degree=3; - gamma=0; - coef0=0; - - nu=algo.nu; - if(nu>0) - svm_type=1; - end - - s=whos('libsvm_cachesize','global'); - - if (length(s)>0) - global libsvm_cachesize; - cachesize=libsvm_cachesize; - else - cachesize=40; - end - if algo.algorithm.verbosity>1 - fprintf('Using %d MB Cache for Libsvm\n',cachesize) - end - C=algo.C; - - - if(C==Inf) - C=10^6; - end - - %eps=1e-3; - %p=0.1; - %shrinking=1; - - eps=1e-5; p=0.001; shrinking=1; - - weight_label=[]; - weight=[]; - nr_weight=0; - - if strcmp(algo.child.ker,'linear') - kernelType = 0; - end; - if strcmp(algo.child.ker,'poly') - kernelType = 1; - degree = algo.child.kerparam; - coef0 = 1; - gamma = 1; - end; - if strcmp(algo.child.ker,'rbf'), - kernelType = 2; - sigma = algo.child.kerparam; - gamma = 1/(2*sigma^2); - end; - - - x=get_x(retDat); - if strcmp(algo.child.ker,'custom'), - kernelType = 4; - K= algo.child.kerparam; - l = get_dim( retDat); - x = get_index( retDat); - x = [ reshape( x, l, 1) [ 1:l]']; % using x to pass indices in Matrix and real indices - end; - - - y=get_y(retDat); - - arglist={ arglist;{'X',x};{'Y',y}; ... - {'svm_type',svm_type};{'kerneltype',kernelType};... - {'degree',degree};{'gamma',gamma};{'coef0',coef0};... - {'cachesize',cachesize};... - {'C',C};{'eps',eps};... - {'nu',nu}; - {'p',p};{'shrinking',shrinking};... - {'balanced_ridge',algo.balanced_ridge}}; - - if( kernelType == 4) - arglist=[arglist;{'kmatrix',K}]; - - [alpha,xSV,bias0]=libsvm_classifier_spider(arglist); - algo.Xsv=get(retDat, xSV( :, 2)); - else - [alpha,xSV,bias0]=libsvm_classifier_spider(arglist); - algo.Xsv=data(xSV); - end - - alpha = alpha * y(1); - bias0 = bias0 * y(1); - algo.b0 = bias0; - - %% code to find which alphas were actually used - %% in libsvm might be slow but more robust - %% can switch off using algo.cutoff=-2 - if ~isempty(xSV) - if algo.alpha_cutoff>-2 - D=[]; - for i=1:1000:get_dim(retDat) - tak=[i:min(get_dim(retDat),i+999)]; - D=[D;calc(distance,algo.Xsv,get(retDat,[tak]))]; - end - [m1 m2]=min(D); - if length(unique(m2))algo.alpha_cutoff); - algo.alpha = alpha( fin); - algo.Xsv = get( algo.Xsv, fin); - - if algo.algorithm.do_not_evaluate_training_error - retDat=set_x(retDat,get_y(retDat)); - else - retDat=test(algo,retDat); - end - - return -end - -algo.b0=bias0; -fin=find(abs(alpha)>algo.alpha_cutoff); -algo.alpha=alpha(fin); -algo.Xsv=get(retDat,fin); - -if algo.algorithm.do_not_evaluate_training_error - retDat=set_x(retDat,get_y(retDat)); -else - retDat=test(algo,retDat); -end - - - -%% ========================================= -%% helper function for xtrain -%% ========================================= -function deltab=correct_b(d) - - -rs=sign(d.X); -max_n_np= length(find( rs==1 & d.Y==-1)); -max_n_pn= length(find( rs==-1 & d.Y==1)); - - -[rx,I]=sort(-d.X); -rx=-rx; - - -res=[]; - -X=d.X; -Y=d.Y; -for i=1:length(rx) - b=rx(i); - b0=rx(i); - r=sign(rx+b0); - n_np= length(find( r==1 & Y==-1)); - n_pn= length(find( r==-1 & Y==1)); - res=[res;n_pn,n_np,b0]; -end -[a,b]=min( abs(res(:,1)-res(:,2))); -deltab=res(b ,3); - -%r=max(res); -%res=res./repmat(r,length(res),1); diff --git a/CanlabCore/External/umap/umap/canlab_umap_example_iris_dataset.asv b/CanlabCore/External/umap/umap/canlab_umap_example_iris_dataset.asv deleted file mode 100644 index 8634250b..00000000 --- a/CanlabCore/External/umap/umap/canlab_umap_example_iris_dataset.asv +++ /dev/null @@ -1,63 +0,0 @@ -%% load data - -load('FisherIris.mat') -rng('default'); - -%% Run umap - -umap_coords = run_umap(meas); - -% see also 'graphic' cluster output - -%% -create_figure('umap on iris dataset', 1, 3); - -plot(umap_coords(:, 1), umap_coords(:, 2), 'ko'); - -xlabel('umap(1)'); ylabel('umap(2)'); -title('UMAP with colors indicating true classes'); - -[indic, names, condf] = string2indicator(species); -colors = scn_standard_colors(length(names)); - -for i = 1:length(names) - - wh = condf == i; - - plot(umap_coords(wh, 1), umap_coords(wh, 2), 'ko', 'MarkerFaceColor', colors{i}); - -end - -%% Cluster data in derived umap space and re-plot - -% note: clusterIdentifiers from run_umap didn't produce great results for me (tor) - -% note: you really want enough (> 2) umap dimensions for this to be meaningful. -% Run again with 3 umap dims -% Will run with same number of dims as original dimensions, but not sure -% yet if this is a good idea. - -[umap_coords3d, umap, umap_clusterIdentifiers, extras] = run_umap(meas, 'cluster_output', 'numeric', 'n_components', 3); - -clusterIdentifiers = clusterdata(umap_coords, 'linkage','ward','savememory','on','maxclust', 4); - -%% re-plot - -subplot(1, 3, 2) - -plot(umap_coords(:, 1), umap_coords(:, 2), 'ko'); - -xlabel('umap(1)'); ylabel('umap(2)'); -title('UMAP with colors indicating estimated clusters'); - -n = length(unique(clusterIdentifiers)); - -colors = scn_standard_colors(n); - -for i = 1:n - - wh = clusterIdentifiers == i; - - plot(umap_coords(wh, 1), umap_coords(wh, 2), 'ko', 'MarkerFaceColor', colors{i}); - -end \ No newline at end of file diff --git a/CanlabCore/Filename_tools/canlab_get_underlay_image.m b/CanlabCore/Filename_tools/canlab_get_underlay_image.m index ed5e4c17..b7f5b61c 100644 --- a/CanlabCore/Filename_tools/canlab_get_underlay_image.m +++ b/CanlabCore/Filename_tools/canlab_get_underlay_image.m @@ -1,23 +1,63 @@ function underlay = canlab_get_underlay_image(varargin) -% Get the name of an anatomical image to use as the underlay for orthviews and other displays +% canlab_get_underlay_image Resolve an anatomical underlay image filename to a full path. % -% No inputs: Use the default underlay +% :Usage: +% :: % -% Or enter an argument with one of these strings: +% underlay = canlab_get_underlay_image +% underlay = canlab_get_underlay_image(keyword_or_filename) % -% 'spm2' 'spm2_single_subj_T1_scalped.img' -% 'colin' 'SPM8_colin27T1_seg.img' -% 'keuken' 'keuken_2014_enhanced_for_underlay.img' -% 'icbm2009c', 'fmriprep20' 'fmriprep20_template.nii.gz' -% 'icbm2009c_0.5mm' 'tpl-MNI152NLin2009bAsym_res-1_T1w.nii.gz' -% 'mni152_1mm' 'MNI152NLin6Asym_T1_1mm.nii.gz' -% 'mni152_withskull' 'MNI152NLin6Asym_T1_1mm.nii.gz' -% 'mni152' 'spm152.nii' -% ...or your custom underlay filename. +% Returns the full path to an anatomical image to be used as the +% underlay for orthviews and other displays. With no inputs, the +% current default underlay ('fmriprep20_template.nii.gz') is returned. +% Otherwise, a known keyword or a custom filename can be provided. % -% Examples: -% underlay = canlab_get_underlay_image -% underlay = canlab_get_underlay_image('icbm2009c_0.5mm') +% If the requested file is not found on the path, the function tries +% several fallback resolutions (deblank, basename only, stripping or +% adding a .gz extension) before giving up with a warning. +% +% :Inputs: +% +% None required. Optionally: +% +% **keyword_or_filename:** +% One of the keywords below, or a custom underlay filename. If +% empty, the default underlay is used. +% +% :Optional Inputs: +% +% The single varargin argument may be one of the following keywords: +% +% :: +% +% 'spm2' 'spm2_single_subj_T1_scalped.img' +% 'colin' 'SPM8_colin27T1_seg.img' +% 'keuken' 'keuken_2014_enhanced_for_underlay.img' +% 'icbm2009c', 'fmriprep20' 'fmriprep20_template.nii.gz' +% 'icbm2009c_0.5mm' 'tpl-MNI152NLin2009bAsym_res-1_T1w.nii.gz' +% 'mni152_1mm' 'MNI152NLin6Asym_T1_1mm.nii.gz' +% 'mni152_withskull' 'MNI152NLin6Asym_T1_1mm.nii.gz' +% 'mni152' 'spm152.nii' +% +% ...or your custom underlay filename. +% +% :Outputs: +% +% **underlay:** +% Full path to the resolved underlay image on disk. Empty (and a +% warning is printed) if no matching file can be found on the +% MATLAB path. +% +% :Examples: +% :: +% +% underlay = canlab_get_underlay_image; +% underlay = canlab_get_underlay_image('icbm2009c_0.5mm'); +% +% :See also: +% - canlab_results_fmridisplay +% - fmridisplay +% - which current_default = 'fmriprep20_template.nii.gz'; diff --git a/CanlabCore/Filename_tools/canlab_list_files.m b/CanlabCore/Filename_tools/canlab_list_files.m index f47d240a..ca7daae3 100644 --- a/CanlabCore/Filename_tools/canlab_list_files.m +++ b/CanlabCore/Filename_tools/canlab_list_files.m @@ -1,26 +1,64 @@ function [names, isfile, isdir, hasduplicates] = canlab_list_files(varargin) -% [names, isfile, isdir, hasduplicates] = canlab_list_files(dir1, dir2, dir3, etc.) -% -% Lists files in a series of subfolders in a particular order. -% Enter as many dirs as you want, which are subfolders. -% If a dir is a cell array of names, this function will look in all combos -% of those cells with other dir strings, in order. -% dir1...n strings are concatenated in order. -% -% Returns: -% Vector of whether each name in names is an existing file, and whether -% each is an existing directory -% -% e.g., -% subjects = {'nsf1' 'nsf2' 'NSF909'}; -% names = canlab_list_files(subjects, 'Structural', 'SPGR', 'wspgr.img') -% names = canlab_list_files(pwd, subjects, 'Structural', 'SPGR', 'wspgr.img') -% -% Return list of whether each is an existing file or dir -% [names, isfile, isdir, hasduplicates] = canlab_list_files(pwd, subjects, 'Structural', 'SPGR', 'wspgr.img') -% -% Make string matrix: -% names = char(names{:}); +% canlab_list_files Build a list of file paths by concatenating subfolder names with optional cell expansion. +% +% :Usage: +% :: +% +% [names, isfile, isdir, hasduplicates] = canlab_list_files(dir1, dir2, dir3, ...) +% +% Lists files in a series of subfolders, in a particular order. Enter +% as many dirs as you want; they are treated as nested subfolders and +% concatenated in order with fullfile. +% +% If any dir argument is a cell array of names, this function will +% expand it: it will produce one output entry for every combination of +% that cell with the other dir strings (in order). This is convenient +% for building per-subject file lists. +% +% :Inputs: +% +% **dir1, dir2, ...:** +% Each argument is either a string (treated as a single +% subfolder/filename appended to all current paths) or a cell +% array of strings (treated as alternatives, each one expanding +% the current list of paths). Strings are concatenated in order +% with fullfile. +% +% :Outputs: +% +% **names:** +% Cell array of constructed paths. +% +% **isfile:** +% Numeric/logical column vector; whether each entry in names is +% an existing file (per exist(name, 'file')). +% +% **isdir:** +% Numeric/logical column vector; whether each entry in names is +% an existing directory (per exist(name, 'dir')). +% +% **hasduplicates:** +% Logical scalar; true if any entries in names are +% duplicates of one another. +% +% :Examples: +% :: +% +% subjects = {'nsf1' 'nsf2' 'NSF909'}; +% names = canlab_list_files(subjects, 'Structural', 'SPGR', 'wspgr.img'); +% names = canlab_list_files(pwd, subjects, 'Structural', 'SPGR', 'wspgr.img'); +% +% % Return list of whether each is an existing file or dir +% [names, isfile, isdir, hasduplicates] = ... +% canlab_list_files(pwd, subjects, 'Structural', 'SPGR', 'wspgr.img'); +% +% % Convert the resulting cell array to a char string matrix +% names = char(names{:}); +% +% :See also: +% - filenames +% - dir +% - fullfile d = varargin; diff --git a/CanlabCore/Filename_tools/expand_4d_filenames.m b/CanlabCore/Filename_tools/expand_4d_filenames.m index c25adace..fa9014e2 100644 --- a/CanlabCore/Filename_tools/expand_4d_filenames.m +++ b/CanlabCore/Filename_tools/expand_4d_filenames.m @@ -102,22 +102,18 @@ % % end % % else - switch(spm('ver')) + switch spm('Ver') case 'SPM2' V = spm_vol(img_name); - fp = fopen(img_name); fseek(fp,0,'eof'); Len = ftell(fp); fclose(fp); n = Len/(prod(V.dim(1:3))*spm_type(V.dim(4),'bits')/8); - - case {'SPM5', 'SPM8','SPM12', 'SPM25'} + otherwise + % SPM5+, including any future versions V = spm_vol(img_name); n = length(V); - - otherwise - error('Unknown SPM version "%s": neuroscientists of the future, fix me!', spm('Ver')); end % end diff --git a/CanlabCore/Filename_tools/scan_get_files.m b/CanlabCore/Filename_tools/scan_get_files.m index 793e4749..f82e97df 100644 --- a/CanlabCore/Filename_tools/scan_get_files.m +++ b/CanlabCore/Filename_tools/scan_get_files.m @@ -11,10 +11,10 @@ wd = pwd(); end - switch(spm('Ver', [], 1)) + switch spm('Ver', [], 1) case 'SPM2' files = spm_get(n, filt, mesg, wd); - case {'SPM5', 'SPM8'} + otherwise % SPM5+, including any future versions if(any(n < 0)) typ = 'dir'; n = abs(n); diff --git a/CanlabCore/GLM_Batch_tools/canlab_glm_convolve.m b/CanlabCore/GLM_Batch_tools/canlab_glm_convolve.m index 2b963f9f..f4c716f8 100644 --- a/CanlabCore/GLM_Batch_tools/canlab_glm_convolve.m +++ b/CanlabCore/GLM_Batch_tools/canlab_glm_convolve.m @@ -1,46 +1,71 @@ -% X = canlab_glm_convolve(stimObj, dsgn, nscan, basis) +% canlab_glm_convolve Convolve SPM-style conditions with a basis function to build a regressor. % -% This function takes an spm style conditions object (see below), a canlab -% DSGN object (see below), a frame count and a basis function type and +% :Usage: +% :: +% +% [X, bf] = canlab_glm_convolve(stimObj, dsgn, nscan, basis, [hrfParam]) +% +% Takes an SPM-style conditions object (see below), a CANlab DSGN +% object (see below), a frame count, and a basis function type, and % returns a timeseries vector corresponding to onsets and durations % specified in stimObj and any modulations specified in dsgn, convolved -% with the basis function specified by basis. Useful if you want to treat -% experimental factors as confounds in a multiple regressors matrix instead -% of treating them as events in SPM. Primarly desinged to allow for -% multiple different basis functions to be used for different design -% factors (e.g. spline interpolation for factors of interest and canonical -% interpolation for confounding factors; here you would implement the -% confounding factors as multiple regressor columns instead after -% convolution). +% with the basis function specified by basis. +% +% Useful if you want to treat experimental factors as confounds in a +% multiple regressors matrix instead of treating them as events in SPM. +% Primarily designed to allow for multiple different basis functions to +% be used for different design factors (e.g., spline interpolation for +% factors of interest and canonical interpolation for confounding +% factors; here you would implement the confounding factors as +% multiple-regressor columns instead, after convolution). +% +% Written by Bogdan Petre, 5/19/2021. +% +% :Inputs: +% +% **stimObj:** +% An SPM-style conditions object. See +% canlab_glm_subject_levels('dsgninfo') under DSGN.conditions +% for an example. % -% Input :: +% **dsgn:** +% CANlab DSGN structure. See +% canlab_glm_subject_levels('dsgninfo') for how to format this +% object. % -% stimObj - an SPM-stype condition object. see -% canlab_glm_subject_levels('dsgninfo') under -% DSGN.conditions for an example +% **nscan:** +% Number of TRs in the scan for which stimObj applies. % -% dsgn - see canlab_glm_subject_levels('dsgninfo') for how to -% format this object. +% **basis:** +% Basis function name. Currently only 'hrf' is supported, +% although others ('spline', 'fourier', 'fourier_han', 'gamma', +% 'fir') are partially wired in and could be implemented easily +% enough. See spm_get_bf.m for example implementations of other +% basis functions; the implementation here follows that function +% closely. % -% nscan - number of TRs in the scan for which stimObj applies. +% :Optional Inputs: % -% basis - currently only 'hrf' is supported, although others could -% be implemented easily enough. See spm_get_bf.m for some -% example implementations of other basis functions. The -% implementation here follows that function closely. -% Optional Input :: +% **hrfParam:** +% A struct following the basis argument. If specifying +% spline or hrf-family bases that need it, the struct must +% provide windowlength, order, and (for splines) +% degree fields. % -% hrfParam - a struct following the basis argument. If specifying -% spline or hrf you will need to specify a structure with -% windowlength, order, and (for splines) degree -% properties. +% :Outputs: % -% Output :: +% **X:** +% A timeseries vector corresponding to the stimuli specified in +% stimObj convolved with the chosen basis function. % -% X - A timeseries vector corresponding to the stimuli -% specified in stimObj. +% **bf:** +% The basis function (or set of basis functions) used for the +% convolution. % -% Written by Bogdan Petre, 5/19/2021 +% :See also: +% - canlab_glm_subject_levels +% - spm_get_bf +% - spm_hrf function [X, bf] = canlab_glm_convolve(stimObj, dsgn, nscan, basis, varargin) if ~isempty(varargin) diff --git a/CanlabCore/GLM_Batch_tools/canlab_glm_getinfo.m b/CanlabCore/GLM_Batch_tools/canlab_glm_getinfo.m index 26674e0d..fd08133a 100644 --- a/CanlabCore/GLM_Batch_tools/canlab_glm_getinfo.m +++ b/CanlabCore/GLM_Batch_tools/canlab_glm_getinfo.m @@ -1,79 +1,123 @@ function [infos] = canlab_glm_getinfo(modeldir,varargin) -% :SUBJECT LEVEL input: +% canlab_glm_getinfo Inspect an SPM subject-level or robfit group-level GLM analysis directory. +% +% :Usage: % :: % -% INFO = canlab_glm_getinfo(spm_subject_level_directory, option, [n]) +% % SUBJECT-LEVEL form +% INFO = canlab_glm_getinfo(spm_subject_level_directory, option, [n]) % -% Get information out of an spm subject level analysis. +% % GROUP-LEVEL form +% INFO = canlab_glm_getinfo(robfit_group_level_directory, option, [n]) % -% :Options: (each option can be called by the listed letter or word + a number, when noted) +% Get information out of an SPM subject-level or robfit group-level +% analysis directory and (for some options) return it in an INFO +% struct. The function dispatches based on what is found in +% modeldir: an SPM.mat triggers subject-level mode; one or more +% robust####/SETUP.mat files trigger group-level mode. % -% **i' 'input':** -% number of volumes and (first) volume name for each run +% Each option can be called by the listed letter or word, and may be +% suffixed by an integer n to restrict the output to a particular +% session, contrast, or input. % -% **'b' 'betas' [n]:** -% beta names (for nth session) +% Any of the subject-level options can be used on a group-level robfit +% analysis by prefixing '1i' (output is then generated based on the +% first input to the first robust analysis). % -% **'B' 'taskbetas' [n]:** -% beta names (that didn't come from multiple regressors) (for nth session) +% :Assumptions: +% In some options, the first contrasts and group-level analysis +% directories are assumed to represent the rest, which may not be +% the case. % -% **'c' 'cons' [n]:** -% contrast names (for nth contrast) +% :Note: +% Group-level options do not yet return a usable INFO struct. % -% **'C' 'conw' [n]:** -% beta names and weights for contrasts (for nth con) +% :Inputs: % -% **'v' 'image' [n]:** -% create figure of design matrix (for nth session) -% (design matrix is multiplied by 100 for visibility) -% (works well for multiple runs) +% **modeldir:** +% Path to a directory containing either an SPM.mat (for +% subject-level analyses) or one or more robust#### +% subdirectories with SETUP.mat (for robfit group-level +% analyses). % -% **'V' 'taskimage' [n]:** -% same as 'image', but only for task betas +% **option:** +% Character/string keyword selecting what to extract. See +% :Optional Inputs: below. % -% **'imagesc':** -% same as 'image', but uses imagesc -% (works well for single runs) +% **n** (optional): +% Integer index used by some options (session number, contrast +% number, or analysis index). % +% :Optional Inputs: % -% :GROUP LEVEL input: -% :: +% Subject-level options: % -% INFO = canlab_glm_getinfo(robfit_group_level_directory, option, [n]) +% **'i', 'input':** +% Number of volumes and (first) volume name for each run. % -% Get information out of a robfit group level analysis. +% **'b', 'betas' [n]:** +% Beta names (for nth session). % -% :Options: (each option can be called by the listed word or letter + a number, when noted) +% **'B', 'taskbetas' [n]:** +% Beta names that didn't come from multiple regressors (for nth +% session). % -% Any of the subject level options can be used on a group level robfit -% analysis by prefixing '1i' (output is generated based on the first input -% to the first robust analysis). +% **'c', 'cons' [n]:** +% Contrast names (for nth contrast). % -% Ex: -% :: +% **'C', 'conw' [n]:** +% Beta names and weights for contrasts (for nth con). % -% canlab_glm_getinfo('second_level/model3','1iconw') +% **'v', 'image' [n]:** +% Create a figure of the design matrix (for nth session). The +% design matrix is multiplied by 100 for visibility. Works well +% for multiple runs. % +% **'V', 'taskimage' [n]:** +% Same as 'image', but only for task betas. % -% **'i' 'input' [n]:** -% input contrasts by number and name (for nth analysis) +% **'imagesc':** +% Same as 'image', but uses imagesc. Works well for single runs. % -% **'I' 'allinput' [n]:** -% input images (for nth analysis) +% Group-level options: % -% **'m' 'model':** -% weights by subject (i.e., directory containing input contrast images) +% **'i', 'input' [n]:** +% Input contrasts by number and name (for nth analysis). % -% **'M' 'allmodels' [n]:** -% weights and input images (for nth analysis) +% **'I', 'allinput' [n]:** +% Input images (for nth analysis). % -% :Assumptions: -% In some options, the first contrasts and group level analysis -% directories are assumed to represent the rest, which may not be the -% case. +% **'m', 'model':** +% Weights by subject (i.e., directory containing input contrast +% images). % -% :Note: -% group level options do not yet return a usable INFO struct. +% **'M', 'allmodels' [n]:** +% Weights and input images (for nth analysis). +% +% :Outputs: +% +% **infos:** +% Struct with fields populated by the requested option (for +% subject-level analyses). For group-level options, this is not +% yet a usable INFO struct. +% +% :Examples: +% :: +% +% % Print number of volumes and first volume name per run +% canlab_glm_getinfo('subj01/model1', 'input') +% +% % List contrast names from a subject-level analysis +% canlab_glm_getinfo('subj01/model1', 'cons') +% +% % Use a subject-level option through a group-level analysis dir +% canlab_glm_getinfo('second_level/model3', '1iconw') +% +% :See also: +% - canlab_glm_subject_levels +% - canlab_glm_group_levels +% - robfit +% - scn_spm_design_check if nargin ~= 2 && nargin ~= 3 error('USAGE: INFO = canlab_glm_getinfo(spm_dir|robfit_dir,option,[n])') diff --git a/CanlabCore/GLM_Batch_tools/canlab_glm_group_levels.m b/CanlabCore/GLM_Batch_tools/canlab_glm_group_levels.m index 1af1f282..9550da6e 100644 --- a/CanlabCore/GLM_Batch_tools/canlab_glm_group_levels.m +++ b/CanlabCore/GLM_Batch_tools/canlab_glm_group_levels.m @@ -1,148 +1,168 @@ function canlab_glm_group_levels(varargin) -% Performs group level robust GLM analysis with robfit -% 1. sets up analysis -% 2. runs robfit -% 3. (optionally) make inverse p maps (for FSL viewing) -% 4. (optionally) estimate significant cluster sizes -% 5. publishes analysis with robfit_results_batch +% canlab_glm_group_levels Run group-level robust GLM analysis with robfit. % % :Usage: % :: % % canlab_glm_group_levels([options]) % -% :Optional Inputs: -% -% **'s', subjects:** -% cell array of filenames of subject-level analysis directories IN -% modeldir -% (note: modeldir won't be prepended to absolute paths) -% -% **'m', modeldir:** -% filename of directory containing subject level analyses +% Performs group-level robust GLM analysis with robfit. The function: % -% **'o', grpmodeldir:** -% output directory name -% -% **'c', cov:** -% a matrix describing group level model -% (do not include intercept, it is automatically included as first regressor) +% 1. Sets up the analysis. +% 2. Runs robfit. +% 3. (Optionally) Makes inverse p maps (for FSL viewing). +% 4. (Optionally) Estimates significant cluster sizes. +% 5. Publishes analysis with robfit_results_batch. % -% see help robfit +% A covfile will cause other specifications of subject, cov, and +% covnames to be ignored. If parameters are defined more than once +% (e.g., modeldir or subjects), only the last entered option will count. % -% note: requires specifying an output directory name -% -% note: ordering of inputs (rows) must match subjects ordering +% .. +% ---------------------------------------------------------------------- +% Copyright (C) 2013 Luka Ruzic % -% **'n', covname:** -% a cell array of names for the covariates in cov +% This program is free software: you can redistribute it and/or modify +% it under the terms of the GNU General Public License as published by +% the Free Software Foundation, either version 3 of the License, or +% (at your option) any later version. % -% **'f', covfile:** -% a csv file will specify the group level model: -% first column, header 'subject', contains names of subject -% directories (to be found in modeldir) -% subsequent columns have covariates, headers are names of covariates -% name of covfile will be name of group analysis directory (placed in -% grpmodeldir) +% This program is distributed in the hope that it will be useful, +% but WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +% GNU General Public License for more details. % -% **'mask', maskimage:** -% filename of mask image +% You should have received a copy of the GNU General Public License +% along with this program. If not, see . +% ---------------------------------------------------------------------- % -% **DSGN:** -% will use the following fields of the DSGN structure: -% modeldir = DSGN.modeldir +% Programmers' notes +% NOT READY YET: +% 'grf' +% estimate significant cluster sizes for each contrast using GRF theory +% will run at a=.05, voxelwise thresholds of .05, .01, .005, .001 +% see help estimate_cluster_extent +% .. % -% subjects = DSGN.subjects +% :Inputs: % -% maskimage = DSGN.mask +% None required. All inputs are optional and supplied as keyword/value +% pairs (see :Optional Inputs: below). A DSGN struct may also be +% passed as a positional argument. % -% :Note: -% A covfile will cause other specifications of subject, cov, and -% covnames to be ignored. +% :Optional Inputs: % -% If parameters are defined more than once (e.g., modeldir or subjects), -% only the last entered option will count. +% **'s', subjects:** +% Cell array of filenames of subject-level analysis directories +% IN modeldir. (Note: modeldir won't be prepended to absolute +% paths.) % -% :Defaults: +% **'m', modeldir:** +% Filename of directory containing subject-level analyses. % -% **subjects:** -% all SPM.mat-containing directories in modeldir +% **'o', grpmodeldir:** +% Output directory name. % -% **modeldir:** -% pwd +% **'c', cov:** +% A matrix describing the group-level model. Do not include the +% intercept; it is automatically included as the first regressor. +% See help robfit. Note: requires specifying an output directory +% name. Note: ordering of inputs (rows) must match the subjects +% ordering. % -% **grpmodeldir:** -% modeldir/one_sample_t_test +% **'n', covname:** +% A cell array of names for the covariates in cov. % -% **cov:** -% {} (run 1 sample t-test, see help robfit) +% **'f', covfile:** +% A CSV file that specifies the group-level model. First column, +% with header 'subject', contains names of subject directories +% (to be found in modeldir). Subsequent columns have covariates; +% headers are names of covariates. The name of covfile will be +% the name of the group analysis directory (placed in +% grpmodeldir). % -% **covname:** -% 'groupmean' +% **'mask', maskimage:** +% Filename of mask image. % -% **mask:** -% 'brainmask.nii' +% **DSGN:** +% A DSGN structure (passed positionally). The following fields +% are used: % -% :Options: +% - modeldir = DSGN.modeldir +% - subjects = DSGN.subjects +% - maskimage = DSGN.mask % % **'README':** -% prints canlab_glm_README, an overview of canlab_glm_{subject,group}_levels +% Prints canlab_glm_README, an overview of +% canlab_glm_{subject,group}_levels. % % **'overwrite':** -% overwrite existing output directories +% Overwrite existing output directories. % % **'noresults':** -% don't run/publish robfit_results_batch +% Do not run/publish robfit_results_batch. % % **'onlyresults':** -% just run/publish robfit_results_batch, don't run robfit (assumes existing analyses) +% Just run/publish robfit_results_batch; do not run robfit +% (assumes existing analyses). % -% **'whichcons', [which cons] -% vector of contrasts to analyze (DEFAULT: aall subject level contrasts) -% see [which cons] in help robfit +% **'whichcons', [which cons]:** +% Vector of contrasts to analyze (default: all subject-level +% contrasts). See [which cons] in help robfit. % % **'invp' [, target_space_image]:** -% generate inverse p maps and resample to the voxel and image dimensions -% of target_space_image -% (viewable on dream with ~ruzicl/scripts/invpview) +% Generate inverse p maps and resample to the voxel and image +% dimensions of target_space_image (viewable on dream with +% ~ruzicl/scripts/invpview). % % **'nolinks':** -% do not make directory of named links to robust directories (using contrast names) +% Do not make directory of named links to robust directories +% (using contrast names). % % **'dream':** -% if you're running on the dream cluster, this option will cause -% all analyses (e.g., lower level contrasts) to be run in parallel -% (submitted with matlab DCS and the Sun Grid Engine) -% Note: currently only works with MATLAB R2009a +% If you're running on the dream cluster, this option will cause +% all analyses (e.g., lower-level contrasts) to be run in +% parallel (submitted with matlab DCS and the Sun Grid Engine). +% Note: currently only works with MATLAB R2009a. % % **'email', address:** -% send notification email to address when done running +% Send notification email to address when done running. % -% .. -% ---------------------------------------------------------------------- -% Copyright (C) 2013 Luka Ruzic -% -% This program is free software: you can redistribute it and/or modify -% it under the terms of the GNU General Public License as published by -% the Free Software Foundation, either version 3 of the License, or -% (at your option) any later version. -% -% This program is distributed in the hope that it will be useful, -% but WITHOUT ANY WARRANTY; without even the implied warranty of -% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -% GNU General Public License for more details. -% -% You should have received a copy of the GNU General Public License -% along with this program. If not, see . -% ---------------------------------------------------------------------- +% :Defaults: % -% Programmers' notes -% NOT READY YET: -% 'grf' -% estimate significant cluster sizes for each contrast using GRF theory -% will run at a=.05, voxelwise thresholds of .05, .01, .005, .001 -% see help estimate_cluster_extent -% .. +% - subjects: All SPM.mat-containing directories in modeldir. +% - modeldir: pwd +% - grpmodeldir: modeldir/one_sample_t_test +% - cov: {} (run 1-sample t-test; see help robfit) +% - covname: 'groupmean' +% - mask: 'brainmask.nii' +% +% :Outputs: +% +% None returned to the workspace. Side effects include the group +% analysis directory tree produced by robfit, optional inverse p +% maps, optional cluster-extent estimates, optional named-link +% directories, and a log file in canlab_glm_logs. +% +% :Examples: +% :: +% +% % 1-sample t-test across all subjects in modeldir +% canlab_glm_group_levels('m', '/path/to/model_dir') +% +% % Use a CSV-defined group model and write to a specific output dir +% canlab_glm_group_levels('m', '/path/to/model_dir', ... +% 'f', 'group_design.csv', 'o', '/path/to/group_out') +% +% % Re-publish results without re-running robfit +% canlab_glm_group_levels('onlyresults', 'm', '/path/to/model_dir') +% +% :See also: +% - canlab_glm_subject_levels +% - canlab_glm_publish_group_levels +% - robfit +% - robfit_results_batch +% - estimate_cluster_extent STARTTIME = datestr(now,31); % SET UP diff --git a/CanlabCore/GLM_Batch_tools/canlab_glm_group_levels_run1input.m b/CanlabCore/GLM_Batch_tools/canlab_glm_group_levels_run1input.m index f56f9559..601c5648 100644 --- a/CanlabCore/GLM_Batch_tools/canlab_glm_group_levels_run1input.m +++ b/CanlabCore/GLM_Batch_tools/canlab_glm_group_levels_run1input.m @@ -1,10 +1,46 @@ function [robfitstatus] = canlab_glm_group_levels_run1input(wd, c) -% child process of canlab_glm_group_levels -% (see canlab_glm_README.txt for an overview) +% canlab_glm_group_levels_run1input Worker process that runs robfit for one input contrast. +% +% :Usage: +% :: +% +% robfitstatus = canlab_glm_group_levels_run1input(wd, c) +% +% Child process of canlab_glm_group_levels. Loads the saved environment +% file env_.mat for contrast index c from the working +% directory wd, switches to the group-level model directory, and +% calls robfit(EXPT, includedcons(c), 0, EXPT.mask) for that single +% contrast. See canlab_glm_README.txt for an overview of the +% canlab_glm_* batch tools. +% +% This function is normally called automatically by +% canlab_glm_group_levels (including in parallel/cluster modes); it is +% rarely invoked directly by users. % % .. % Copyright (C) 2013 Luka Ruzic % .. +% +% :Inputs: +% +% **wd:** +% Working directory containing the saved environment file +% env_.mat for the contrast. +% +% **c:** +% Integer index into includedcons (1-based) selecting which +% contrast's robfit job to run. Used to locate +% env_.mat. +% +% :Outputs: +% +% **robfitstatus:** +% Status code: 1 on success, -1 on error. +% +% :See also: +% - canlab_glm_group_levels +% - canlab_glm_subject_levels +% - robfit load(fullfile(wd,sprintf('env_%04d',c))); %% PREP diff --git a/CanlabCore/GLM_Batch_tools/canlab_glm_publish_group_levels.m b/CanlabCore/GLM_Batch_tools/canlab_glm_publish_group_levels.m index 2c88115b..715c0cab 100644 --- a/CanlabCore/GLM_Batch_tools/canlab_glm_publish_group_levels.m +++ b/CanlabCore/GLM_Batch_tools/canlab_glm_publish_group_levels.m @@ -1,3 +1,53 @@ +% canlab_glm_publish_group_levels Publish group-level robust regression results via robust_results_batch. +% +% :Usage: +% :: +% +% % Run from a directory containing robust* subdirectories +% canlab_glm_publish_group_levels +% +% Child script of canlab_glm_publish. Runs robust_results_batch in a +% publishable manner so the results can be rendered as an HTML report +% via MATLAB's publish(). +% +% Behavior: +% +% - If a variable EXPT exists in the workspace, contrast names +% and robust#### directories are taken from EXPT.SNPM. +% - Otherwise the script discovers robust[0-9][0-9][0-9][0-9] +% directories in the current working directory; if none are found, +% the current directory itself is treated as a single robust* +% directory. Contrast names are read from each directory's +% SETUP.mat / SPM.mat. +% - A small wrapper script robfit_results.m is written and then +% published. The output HTML report is placed in +% Robust_Regression_Results_html under the working directory. +% +% Workspace variables thresh and size (passed to +% robust_results_batch) default to [.001 .005 .05] and [5 1 1] +% respectively if not already defined. +% +% :Inputs: +% +% None. This file is a script and operates on the current working +% directory and the workspace variables EXPT, thresh, and +% size if they exist. +% +% :Outputs: +% +% No MATLAB outputs. Side effects: +% +% - Creates Robust_Regression_Results_html directory with the +% published HTML report. +% - Creates and then deletes the helper script robfit_results.m +% in the working directory. +% +% :See also: +% - canlab_glm_publish_subject_levels +% - canlab_glm_group_levels +% - robust_results_batch +% - publish + % child script of canlab_glm_publish % runs robust_results_batch in publishable manner % runs in directory containing robust* directories diff --git a/CanlabCore/GLM_Batch_tools/canlab_glm_publish_subject_levels.m b/CanlabCore/GLM_Batch_tools/canlab_glm_publish_subject_levels.m index 3951df59..f053ecfe 100644 --- a/CanlabCore/GLM_Batch_tools/canlab_glm_publish_subject_levels.m +++ b/CanlabCore/GLM_Batch_tools/canlab_glm_publish_subject_levels.m @@ -1,3 +1,47 @@ +% canlab_glm_publish_subject_levels Publish subject-level SPM design reviews via scn_spm_design_check. +% +% :Usage: +% :: +% +% % Run from a directory containing subject-level SPM analyses +% canlab_glm_publish_subject_levels +% +% Child script of canlab_glm_publish. Runs scn_spm_design_check in a +% publishable manner (intended for use with MATLAB's publish() so the +% review can be rendered as HTML/PDF). +% +% Behavior: +% +% - If the current working directory itself contains an SPM.mat file, +% it is treated as the single subject-level analysis to review. +% - Otherwise the script searches one directory level deep for any +% subdirectories that contain an SPM.mat file and reviews each. +% - For every located analysis, scn_spm_design_check(..., 'events_only') +% is called and snapnow is issued so that figures appear in the +% published output. +% +% A diary log of the review is written to +% model_review_work_log.txt in the current working directory. +% +% :Inputs: +% +% None. This file is a script and operates on the current working +% directory. +% +% :Outputs: +% +% No MATLAB outputs. Side effects: +% +% - model_review_work_log.txt log file in pwd. +% - Figures from scn_spm_design_check captured by snapnow when the +% script is run via MATLAB's publish(). +% +% :See also: +% - canlab_glm_publish_group_levels +% - canlab_glm_subject_levels +% - scn_spm_design_check +% - publish + % child script of canlab_glm_publish % runs scn_spm_design_check in publishable manner % runs in directory containing subject level SPM analyses diff --git a/CanlabCore/GLM_Batch_tools/canlab_glm_subject_levels.m b/CanlabCore/GLM_Batch_tools/canlab_glm_subject_levels.m index be17d7ba..ced952b3 100644 --- a/CanlabCore/GLM_Batch_tools/canlab_glm_subject_levels.m +++ b/CanlabCore/GLM_Batch_tools/canlab_glm_subject_levels.m @@ -1,89 +1,138 @@ function canlab_glm_subject_levels(dsgnarg, varargin) -% Performs lower level GLM analysis with SPM: -% 1. specifies model -% 2. estimates model -% 3. generates contrast images for model -% 4. creates directory with named links to spmT and con maps -% 5. publishes analyses with scn_spm_design_check +% canlab_glm_subject_levels Run lower (single-subject) level GLM analyses with SPM. % % :Usage: % :: % -% canlab_glm_subject_levels(DSGN [options]) +% canlab_glm_subject_levels(DSGN, [options]) +% canlab_glm_subject_levels('README') % -% DSGN struct - defines the model and analysis parameters +% Performs single-subject GLM analysis with SPM. For each subject the +% function: % -% canlab_glm_subject_levels('README') to see description +% 1. Specifies the model. +% 2. Estimates the model. +% 3. Generates contrast images for the model. +% 4. Creates a directory with named links to spmT and con maps. +% 5. Publishes analyses with scn_spm_design_check. % -% :Options: +% Model specification and estimation are done by canlab_spm_fmri_model_job. +% Contrasts are specified by canlab_spm_contrast_job_luka; see that +% function for more information. Use canlab_glm_subject_levels('README') +% to print an overview, and canlab_glm_subject_levels('dsgninfo') to +% print a description of the DSGN structure. +% +% .. +% ------------------------------------------------------------------------- +% Copyright (C) 2013 Luka Ruzic +% +% This program is free software: you can redistribute it and/or modify +% it under the terms of the GNU General Public License as published by +% the Free Software Foundation, either version 3 of the License, or +% (at your option) any later version. +% +% This program is distributed in the hope that it will be useful, +% but WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +% GNU General Public License for more details. +% +% You should have received a copy of the GNU General Public License +% along with this program. If not, see . +% +% Programmers' notes: +% +% Added support for reading help documentation in Windows - +% Michael Sun 09/13/2022 +% .. +% +% :Inputs: +% +% **DSGN:** +% Struct that defines the model and analysis parameters, or the +% path to a .mat file containing such a struct. Use +% canlab_glm_subject_levels('dsgninfo') for a description of +% the DSGN structure. Alternatively, pass the literal string +% 'README' to print canlab_glm_README, or 'dsgninfo' to print +% canlab_glm_dsgninfo. +% +% :Optional Inputs: % % **'README':** -% prints canlab_glm_README, an overview of canlab_glm_{subject,group}_levels +% Prints canlab_glm_README, an overview of +% canlab_glm_{subject,group}_levels. % % **'dsgninfo':** -% prints description of DSGN structure +% Prints description of DSGN structure. % % **'subjects', subject_list:** -% ignore DSGN.subjects, use cell array subject_list +% Ignore DSGN.subjects; use cell array subject_list instead. % % **'overwrite':** -% turn on overwriting of existing analyses (DEFAULT: skip existing) +% Turn on overwriting of existing analyses (default: skip +% existing). % % **'onlycons':** -% only run contrast job (no model specification or estimation) -% note: will overwrite existing contrasts -% note: to not run contrasts, simply do not include a contrasts field in DSGN +% Only run contrast job (no model specification or estimation). +% Note: will overwrite existing contrasts. To not run contrasts, +% simply do not include a contrasts field in DSGN. % % **'addcons':** -% only run contrasts that aren't already in SPM.mat -% option to canlab_spm_contrast_job +% Only run contrasts that aren't already in SPM.mat. Option to +% canlab_spm_contrast_job. % % **'nodelete':** -% do not delete existing contrasts (consider using addcons, above) -% option to canlab_spm_contrast_job +% Do not delete existing contrasts (consider using addcons, +% above). Option to canlab_spm_contrast_job. % % **'nolinks':** -% will not make directory with named links to contrast images +% Do not make directory with named links to contrast images. % % **'noreview':** -% will not run scn_spm_design_check +% Do not run scn_spm_design_check. % % **'dream':** -% if you're running on the dream cluster, this option will cause -% all subjects to be run in parallel (submitted with matlab DCS and -% the Sun Grid Engine) -% Note: currently only works with MATLAB R2009a +% If you're running on the dream cluster, this option will cause +% all subjects to be run in parallel (submitted with matlab DCS +% and the Sun Grid Engine). Note: currently only works with +% MATLAB R2009a. +% +% **'parallel':** +% Run subjects in parallel locally. +% +% **'nocatch':** +% Do not wrap subject-level errors in try/catch. % % **'email', address:** -% send notification email to address when done running +% Send notification email to address when done running. % -% Model specification and estimation done by canlab_spm_fmri_model_job +% :Outputs: % -% Contrasts are specified by canlab_spm_contrast_job_luka -% see that function for more info. +% None returned to the workspace. Side effects include written SPM.mat +% files, contrast images, log files in the canlab_glm_logs folder, +% and (optionally) a directory of named links to contrast images and +% published HTML reports from scn_spm_design_check. % -% .. -% ------------------------------------------------------------------------- -% Copyright (C) 2013 Luka Ruzic -% -% This program is free software: you can redistribute it and/or modify -% it under the terms of the GNU General Public License as published by -% the Free Software Foundation, either version 3 of the License, or -% (at your option) any later version. -% -% This program is distributed in the hope that it will be useful, -% but WITHOUT ANY WARRANTY; without even the implied warranty of -% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -% GNU General Public License for more details. -% -% You should have received a copy of the GNU General Public License -% along with this program. If not, see . +% :Examples: +% :: % -% Programmers' notes: -% -% Added support for reading help documentation in Windows - -% Michael Sun 09/13/2022 -% .. +% % Print the overview README +% canlab_glm_subject_levels('README') +% +% % Print description of the DSGN structure +% canlab_glm_subject_levels('dsgninfo') +% +% % Run a single-subject analysis from an existing DSGN struct +% canlab_glm_subject_levels(DSGN) +% +% % Override DSGN.subjects with a custom list and overwrite existing +% canlab_glm_subject_levels(DSGN, 'subjects', {'sub-01','sub-02'}, 'overwrite') +% +% :See also: +% - canlab_glm_group_levels +% - canlab_glm_publish_subject_levels +% - canlab_spm_fmri_model_job +% - canlab_spm_contrast_job_luka +% - scn_spm_design_check STARTTIME = datestr(now,31); STARTINGDIR = pwd; diff --git a/CanlabCore/GLM_Batch_tools/canlab_glm_subject_levels_old.m b/CanlabCore/GLM_Batch_tools/canlab_glm_subject_levels_old.m deleted file mode 100644 index 2f45d54c..00000000 --- a/CanlabCore/GLM_Batch_tools/canlab_glm_subject_levels_old.m +++ /dev/null @@ -1,690 +0,0 @@ -function canlab_glm_subject_levels_old(dsgnarg, varargin) -% Performs lower level GLM analysis with SPM: -% 1. specifies model -% 2. estimates model -% 3. generates contrast images for model -% 4. creates directory with named links to spmT and con maps -% 5. publishes analyses with scn_spm_design_check -% -% :Usage: -% :: -% -% canlab_glm_subject_levels(DSGN [options]) -% -% DSGN struct - defines the model and analysis parameters -% -% canlab_glm_subject_levels('README') to see description -% -% :Options: -% -% **'README':** -% prints canlab_glm_README, an overview of canlab_glm_{subject,group}_levels -% -% **'dsgninfo':** -% prints description of DSGN structure -% -% **'subjects', subject_list:** -% ignore DSGN.subjects, use cell array subject_list -% -% **'overwrite':** -% turn on overwriting of existing analyses (DEFAULT: skip existing) -% -% **'onlycons':** -% only run contrast job (no model specification or estimation) -% note: will overwrite existing contrasts -% note: to not run contrasts, simply do not include a contrasts field in DSGN -% -% **'addcons':** -% only run contrasts that aren't already in SPM.mat -% option to canlab_spm_contrast_job -% -% **'nodelete':** -% do not delete existing contrasts (consider using addcons, above) -% option to canlab_spm_contrast_job -% -% **'nolinks':** -% will not make directory with named links to contrast images -% -% **'noreview':** -% will not run scn_spm_design_check -% -% **'dream':** -% if you're running on the dream cluster, this option will cause -% all subjects to be run in parallel (submitted with matlab DCS and -% the Sun Grid Engine) -% Note: currently only works with MATLAB R2009a -% -% **'email', address:** -% send notification email to address when done running -% -% Model specification and estimation done by canlab_spm_fmri_model_job -% -% Contrasts are specified by canlab_spm_contrast_job_luka -% see that function for more info. -% -% .. -% ------------------------------------------------------------------------- -% Copyright (C) 2013 Luka Ruzic -% -% This program is free software: you can redistribute it and/or modify -% it under the terms of the GNU General Public License as published by -% the Free Software Foundation, either version 3 of the License, or -% (at your option) any later version. -% -% This program is distributed in the hope that it will be useful, -% but WITHOUT ANY WARRANTY; without even the implied warranty of -% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -% GNU General Public License for more details. -% -% You should have received a copy of the GNU General Public License -% along with this program. If not, see . -% -% Programmers' notes: -% -% Added support for reading help documentation in Windows - -% Michael Sun 09/13/2022 -% .. - -STARTTIME = datestr(now,31); -STARTINGDIR = pwd; -%% SET UP - -% set path -% if exist('canlab_preproc.m','file') ~= 2 -% try -% addpath(genpath('/data/projects/wagerlab/Repository')); -% catch %#ok -% error('Failed to add canlab repository (/data/projects/wagerlab/Repository) to path') -% end -% end -% addpath(genpath('/usr/local/spm/spm8/matlabbatch')); -% addpath(genpath('/usr/local/spm/spm8/apriori')); -% addpath(genpath('/usr/local/spm/spm8/canonical')); -% lukasvo76: commented out because this was old hard-coded stuff - - -% set defaults -diarydirname = 'canlab_glm_logs'; -diaryfilename = ['subject_levels_' regexprep(regexprep(STARTTIME,' ','_'), ':','') '_' num2str(randi(1000000)) '.log']; - -OPTS.parallel_dream = ''; -OPTS.parallel_local = false; -OPTS.overwrite = false; -OPTS.onlycons = false; -OPTS.nocatch = false; -OPTS.run_renaming = true; -OPTS.run_hist = false; % hidden option -OPTS.run_review = true; -OPTS.modeljob = ''; -OPTS.conjob = ''; - - -%% PARSE ARGUMENTS -% get DSGN structure -if ischar(dsgnarg) - if strcmp(dsgnarg,'README') - if ispc() - system(sprintf('type %s',which('canlab_glm_README.txt'))); - else - system(sprintf('cat %s',which('canlab_glm_README.txt'))); - end - return; - elseif strcmp(dsgnarg,'dsgninfo') - if ispc() - system(sprintf('type %s',which('canlab_glm_dsgninfo.txt'))); - else - system(sprintf('cat %s',which('canlab_glm_dsgninfo.txt'))); - end - return; - elseif any(regexp(dsgnarg,'\.mat$')) && exist(dsgnarg,'file') - load(dsgnarg); - else - error('Unrecognized argument: %s', dsgnarg) - end -elseif isstruct(dsgnarg) - DSGN = dsgnarg; -else - error('DSGN structure must be given as first argument, either as a variable or as a matfile.') -end - -% go through varargin -i=1; -while i<=numel(varargin) - if ischar(varargin{i}) - switch(varargin{i}) - case {'README'} - if ispc() - system(sprintf('type %s',which('canlab_glm_README.txt'))); - else - system(sprintf('cat %s',which('canlab_glm_README.txt'))); - end - return; - case {'dream'} - version = ver('matlab'); - switch version.Release - case '(R2009a)' - OPTS.parallel_dream = '2009a'; - case '(R2011a)' - OPTS.parallel_dream = '2011a'; - otherwise - error('Current version of matlab (%s)',version.Release) - end - case {'parallel'} - OPTS.parallel_local = true; - case {'dsgninfo'} - if ispc() - system(sprintf('type %s',which('canlab_glm_README.txt'))); - else - system(sprintf('cat %s',which('canlab_glm_dsgninfo.txt'))); - end - return; - case {'subjects'} - i=i+1; - subjects = varargin{i}; - case {'overwrite'} - OPTS.overwrite = true; - case {'runhist'} % hidden option - OPTS.run_hist = true; - case {'onlycons'} - OPTS.onlycons = true; - case {'addcons'} - OPTS.conjob = [OPTS.conjob ',''addcons''']; - case {'nocatch'} - OPTS.nocatch = true; - case {'nodelete'} - OPTS.conjob = [OPTS.conjob ',''nodelete''']; - case {'nolinks'} - OPTS.run_renaming = false; - case {'noreview'} - OPTS.run_review = false; - case {'email'} - i=i+1; - address = varargin{i}; - otherwise - error(['UNRECOGNIZED OPTION: ' varargin{i}]) - end - else - disp(varargin{i}) - error('Above option UNRECOGNIZED') - end - i=i+1; -end - - - -%% DSGN-parsing -% catch bad fields -allowablefields = {... - 'metadata' ... - 'modeldir' ... - 'subjects' ... - 'funcnames' 'allowmissingfunc' ... - 'concatenation' ... - 'tr' 'hpf' 'fmri_t' 'fmri_t0' ... - 'conditions' 'pmods' 'convolution' 'multireg' 'multiregbehav' 'singletrialsall' 'singletrials' 'ar1' 'fast'... - 'allowmissingcondfiles' 'allowemptycond' 'notimemod' 'modelingfilesdir' ... - 'customrunintercepts' ... - 'contrasts' 'contrastnames' 'contrastweights' ... - 'regmatching' 'defaultsuffix' 'noscale' ... - 'timingcheck', 'orth_off', ... - 'multiregbehav' - }; -actualfields = fieldnames(DSGN); -for i = 1:numel(actualfields) - if isempty(strmatch(actualfields{i},allowablefields,'exact')) - warning('UNRECOGNIZED DSGN FIELD: DSGN.%s', actualfields{i}) - end -end - - -% parse inputs, set defaults for missing inputs, etc -if ~isfield(DSGN,'modeldir') - error('No modeldir specified') - %DSGN.modeldir = pwd; -end - -if exist('subjects','var') - DSGN.subjects = subjects; -else - if ~isfield(DSGN,'subjects') - error('No subjects specified') - end -end -regexprep(DSGN.subjects,'/$',''); - -if ~isfield(DSGN,'contrastnames'), DSGN.contrastnames = {}; end -if ~isfield(DSGN,'contrastweights'), DSGN.contrastweights = {}; end - -if isfield(DSGN,'defaultsuffix') - OPTS.conjob = [OPTS.conjob ',''suffix'',''' DSGN.defaultsuffix '''']; -end - -if isfield(DSGN,'noscale') - if ~islogical(DSGN.noscale) && ~isnumeric(DSGN.noscale) - error('DSGN.noscale must be true/false or 1/0') - end - if DSGN.noscale, OPTS.conjob = [OPTS.conjob ',''noscale''']; end -end - -if isfield(DSGN,'regmatching') - OPTS.conjob = [OPTS.conjob ',''' DSGN.regmatching '''']; -end - - -if ~OPTS.onlycons % check all the model spec/estimation stuff - if ~isfield(DSGN,'funcnames'), error('No functional data specified'); end - if ~isfield(DSGN,'allowmissingfunc'), DSGN.allowmissingfunc = false; end - - if isfield(DSGN,'timingcheck') - if ~isfield(DSGN.timingcheck,'condition') - error('Must define DSGN.timingcheck.condition'); - end - - if ~isfield(DSGN.timingcheck,'mask') - error('Must define DSGN.timingcheck.mask'); - end - if ~isfield(DSGN.timingcheck,'stat') - DSGN.timingcheck.stat = 'mean'; - end - end - - if ~isfield(DSGN,'conditions'), fprintf('WARNING: No conditions specified.\n'); end - if ~isfield(DSGN,'allowmissingcondfiles'), DSGN.allowmissingcondfiles = false; end - if ~isfield(DSGN,'allowemptycond'), DSGN.allowemptycond = false; end - - if ~isfield(DSGN,'convolution') - DSGN.convolution.type = 'hrf'; - DSGN.convolution.time = 0; - DSGN.convolution.dispersion = 0; - end - - if ~isfield(DSGN,'singletrialsall'), DSGN.singletrialsall = false; end - - if ~isfield(DSGN,'ar1'), DSGN.ar1 = false; end - - if ~isfield(DSGN,'fast') - DSGN.fast = false; - else - assert(~(DSGN.fast && DSGN.ar1), "You can only specify one of FAST or AR(1) models at most"); - end - - if isfield(DSGN,'notimemod') && DSGN.notimemod - OPTS.modeljob = [OPTS.modeljob ',''notimemod''']; - end - if ~isfield(DSGN,'modelingfilesdir'), DSGN.modelingfilesdir = 'spm_modeling'; end - - if ~isfield(DSGN,'tr'), error('no TR specified'); end - if ~isfield(DSGN,'hpf'), error('no HPF specified'); end - if isfield(DSGN,'orth_off') - if logical(DSGN.orth_off) - OPTS.modeljob = [OPTS.modeljob ',''orth_off''']; - end - end - if isfield(DSGN,'fmri_t') - OPTS.modeljob = [OPTS.modeljob ',''fmri_t'',' num2str(DSGN.fmri_t)]; - end - if isfield(DSGN,'fmri_t0') - OPTS.modeljob = [OPTS.modeljob ',''fmri_t0'',' num2str(DSGN.fmri_t0)]; - else - error('DSGN.fmri_t0 (microtime onset) not specified') - end - - if isfield(DSGN,'multireg') - DSGN.multireg = [regexprep(DSGN.multireg, '\.mat$', '') '.mat']; - else - DSGN.multireg = ''; - end - - if isfield(DSGN,'multiregbehav') - DSGN.multiregbehav = [regexprep(DSGN.multiregbehav, '\.mat$', '') '.mat']; - else - DSGN.multiregbehav = ''; - end - - if ~isfield(DSGN,'concatenation') - DSGN.concatenation = {}; - end - - % check for uneven numbers of functional files - for s = 1:numel(DSGN.subjects) - for r = 1:numel(DSGN.funcnames) - next(r) = numel(filenames(fullfile(DSGN.subjects{s},DSGN.funcnames{r}))); %#ok - end - if DSGN.allowmissingfunc - if sum(next>1)~=0 - if ~isempty(DSGN.concatenation) - fprintf('ERROR: If allowmissingfunc is set and concatenation is desired,\n') - fprintf(' DSGN.funcnames strings must match one functional data file each\n') - return - elseif numel(DSGN.conditions)>1 - fprintf('ERROR: If allowmissing func is set:') - fprintf(' EITHER only one session''s worth of conditions is specified\n') - fprintf(' OR DSGN.funcnames strings match one functional data file each\n') - return - end - end - elseif s>1 && any(next~=last)~=0 - fprintf('Number of functional images retrieved is not consistent across subjects.\n') - fprintf('(fix this or use DSGN.allowmissingfunc)\n'); - return - end - last = next; - end -end - - - -%% PREP WORK -if ~exist(DSGN.modeldir,'dir') - fprintf('Making subject level models directory: %s\n',DSGN.modeldir) - mkdir(DSGN.modeldir) -else - fprintf('Moving to existing subject level models directory: %s\n',DSGN.modeldir) -end -cd(DSGN.modeldir); - -% initialize SPM's job manager -spm_jobman('initcfg'); - -% initialize statuses -modelstatus = zeros(1,numel(DSGN.subjects)); -constatus = zeros(1,numel(DSGN.subjects)); -linkstatus = zeros(1,numel(DSGN.subjects)); -histstatus = []; % hidden option -reviewstatus = []; -tchkstatus = []; - -% diary - to be on except when calling other functions that do their own logging -diarydir = fullfile(DSGN.modeldir,diarydirname); -if ~exist(diarydir,'dir'), mkdir(diarydir); end -diaryname = fullfile(diarydir,diaryfilename); -fulldiaryname = regexprep(diaryname,'\.log$','_full.log'); -fprintf('Writing to logfile: %s\n',diaryname) -diary(diaryname), fprintf('STARTED: %s\n',STARTTIME), diary off - -% make working directory -wd = regexprep(diaryname,'\.log$',''); -mkdir(wd) - - - -%% RUN LOWER LEVELS -if ~isempty(OPTS.parallel_dream) - % define scheduler - sched = findResource('scheduler', 'type', 'generic'); - set(sched,'ClusterSize', 1); - set(sched,'DataLocation',wd); - switch OPTS.parallel_dream - case '2009a' - set(sched,'ClusterMatlabRoot','/usr/local/matlab/R2009a'); - set(sched,'ParallelSubmitFcn',@sgeParallelSubmitFcn); - set(sched,'SubmitFcn',@sgeSubmitFcn); - set(sched,'DestroyJobFcn', @sgeDestroyJobFcn); - set(sched,'DestroyTaskFcn',@sgeDestroyTaskFcn); - case '2011a' - set(sched,'ClusterMatlabRoot','/usr/local/matlab/R2011a'); - set(sched,'ParallelSubmitFcn',@parallelSubmitFcn); - set(sched,'SubmitFcn',@distributedSubmitFcn); - set(sched,'DestroyJobFcn', @destroyJobFcn); - set(sched,'DestroyTaskFcn',@destroyTaskFcn); - end - set(sched,'ClusterOsType','unix'); - set(sched,'HasSharedFileSystem', true); - nargsout = 3; - - % submit jobs - diary(diaryname), fprintf('%s\tSubmitting jobs to cluster\n',datestr(now,31)); date; diary off - for s = 1:numel(DSGN.subjects) - save(fullfile(wd,sprintf('env_%04d',s)),'DSGN','OPTS','STARTINGDIR'); - - j = sched.createJob(); - set(j,'PathDependencies',cellstr(path)); - createTask(j, str2func('canlab_glm_subject_levels_run1subject'), nargsout, {wd s}); - alltasks = get(j, 'Tasks'); - set(alltasks, 'CaptureCommandWindowOutput', true); - diary(diaryname), fprintf('Submitting analysis for %s\n',DSGN.subjects{s}); diary off - submit(j); - end - - % wait - % look into using wait, waitForState - diary(diaryname), fprintf('WAITING for jobs to stop running (they may run for a while)\n'); diary off - t1 = clock; - notdone = true; - fprintf('Elapsed minutes: ') - while notdone - notdone = false; - for i=1:8, fprintf('\b'); end; fprintf('%8.1f',etime(clock,t1)/60); - pause(10) - jobstatefiles = filenames(sprintf('%s/Job*.state.mat',wd),'absolute'); - for s = 1:numel(jobstatefiles) - jobstate = textread(jobstatefiles{s},'%s'); - if strcmp(jobstate,'running') || strcmp(jobstate,'queued'), notdone = true; break; end - end - end - diary(diaryname), fprintf('\n%s\tJobs done running\n',datestr(now,31)); diary off - - % handle output arguments/streams - diary(diaryname), fprintf('GATHERING job outputs\n'); diary off - jobdirs = filenames(sprintf('%s/Job*[0-9]',wd),'absolute'); - for i = 1:numel(jobdirs) - % load state and output - jobin = load(sprintf('%s/Task1.in.mat',jobdirs{i})); - jobout = load(sprintf('%s/Task1.out.mat',jobdirs{i})); - - % parse output arguments - modelstatus(jobin.argsin{2}) = jobout.argsout{1}; - constatus(jobin.argsin{2}) = jobout.argsout{2}; - linkstatus(jobin.argsin{2}) = jobout.argsout{3}; - - % output stream - cw = jobout.commandwindowoutput; - cw = regexprep(cw,'^> ',''); - cw = regexprep(cw,'\n> ','\n'); - cw = regexprep(cw,'\b[^\n]*\n',' LINES WITH BACKSPACES OMITTED\n'); - diary(fullfile(wd,sprintf('cmdwnd_%04d.txt',jobin.argsin{2}))), disp(cw), diary off - end - % output stream - [ignore ignore] = system(sprintf('cat %s/cmdwnd_*txt > %s',wd,fulldiaryname)); %#ok - - % merge diaries - [ignore ignore] = system(sprintf('rm %s/diary*',wd)); %#ok - [ignore ignore] = system(sprintf('grep ''^> '' %s | sed ''s|^> ||'' >> %s',fulldiaryname,diaryname)); %#ok -else - if OPTS.parallel_local - parfor s = 1:numel(DSGN.subjects) - parsave(fullfile(wd,sprintf('env_%04d',s)),DSGN,OPTS,STARTINGDIR); - [modelstatus(s) constatus(s) linkstatus(s)] = canlab_glm_subject_levels_run1subject_old(wd,s); % lukasvo changed to _old version of function which works with LaBGAS firstlevel code - end - else - for s = 1:numel(DSGN.subjects) - save(fullfile(wd,sprintf('env_%04d',s)),'DSGN','OPTS','STARTINGDIR'); % edited lukasvo76 because 1) parsave is not a standard matlab function and 2) save works fine in ordinary for loop - [modelstatus(s) constatus(s) linkstatus(s)] = canlab_glm_subject_levels_run1subject_old(wd,s); - end - end - - % merge diaries - [ignore ignore] = system(sprintf('cat %s/diary* >> %s',wd,diaryname)); %#ok -end - - -%% POST ANALYSIS PROCESSES -diary(diaryname) -fprintf('\n\n\n') -fprintf('-------------------------------\n') -fprintf('-- POST ANALYSIS PROCESSES --\n') -fprintf('-------------------------------\n') -diary off -if sum(modelstatus==-1) || sum(constatus==-1) - diary(diaryname), fprintf('SKIPPED: some model jobs or contrast jobs failed.\n'), diary off -else - % TIMING CHECK - if isfield(DSGN,'timingcheck') - diary(diaryname), fprintf('\n... GENERATING TIMING CHECK REPORTS\n'), diary off - try - % publish_timing_check(DSGN); - canlab_glm_subject_levels_timingcheck(DSGN); - tchkstatus = 1; - catch exc - if OPTS.nocatch, cd(STARTINGDIR); rethrow(exc) - else diary(diaryname), fprintf('%s\n',getReport(exc,'extended')); diary off; end - tchkstatus = -1; - end - end - - if sum(constatus==1) == 0 - diary(diaryname), fprintf('SKIPPED: no new contrasts have been run.\n'), diary off - else - % T MAP HISTOGRAMS - if OPTS.run_hist % hidden option - diary(diaryname), fprintf('\n... T STATISTIC HISTOGRAMS.\n'), diary off - try - cd(DSGN.modeldir) - batch_t_histograms('o','t_histograms') - close all - histstatus = 1; - catch exc - if OPTS.nocatch, cd(STARTINGDIR); rethrow(exc) - else diary(diaryfile), fprintf('%s\n',getReport(exc,'extended')); diary off; end - histstatus = -1; - end - end - - - - % CANLAB DESIGN REVIEW - diary(diaryname), fprintf('\n... GENERATING DESIGN REVIEWS\n'), diary off - if ~OPTS.run_review - diary(diaryname), fprintf('SKIPPED: switched off\n'), diary off - else - try - canlab_glm_publish('s',DSGN.modeldir); - reviewstatus = 1; - catch exc - if OPTS.nocatch, cd(STARTINGDIR); rethrow(exc) - else diary(diaryfile), fprintf('%s\n',getReport(exc,'extended')); diary off; end - reviewstatus = -1; - end - end - end -end - - -%% failure report -diary(diaryname) -fprintf('\n\n\n') -fprintf('----------------------\n') -fprintf('-- FAILURE REPORT --\n') -fprintf('----------------------\n') -nfail = failure_report(DSGN,modelstatus,constatus,linkstatus,histstatus,reviewstatus,tchkstatus); -diary off - - -%% email notification -if exist('address','var') - try - [ignore output] = system(sprintf('printf "canlab_glm_subject_levels has finished running.\nDirectory: %s\nLog file: %s\nFailure count: %d\n" | mail -v -s "canlab_glm_subject_levels done" %s',DSGN.modeldir,diaryname,nfail,address)); %#ok - catch %#ok - diary(diaryname), fprintf('The notification email failed to send.\n'), diary off - end -end - - -%% clean up -diary(diaryname), fprintf('\n\nFINISHED: %s\n',datestr(now,31)), diary off - -cd(STARTINGDIR) - -end - - - - -% ------------------------------------------------------------------------- -% SUBFUNCTIONS ---------------------------------------------------------- -% ------------------------------------------------------------------------- - -%% -function [nfail] = failure_report(DSGN,modelstatus,constatus,linkstatus,histstatus,reviewstatus,tchkstatus) - -nfail = 0; -if sum(modelstatus == -1) - fprintf('\nFAILED model spec/estim jobs: %d of %d\n',sum(modelstatus==-1),sum(modelstatus~=0)) - fprintf('%s\n',DSGN.subjects{modelstatus == -1}) - nfail = nfail+sum(modelstatus == -1); -end -if sum(constatus == -1) - fprintf('\nFAILED contrast jobs: %d of %d\n',sum(constatus == -1),sum(constatus ~= 0)) - fprintf('%s\n',DSGN.subjects{constatus == -1}) - nfail = nfail + sum(constatus == -1); -end -if sum(linkstatus == -1) - fprintf('\nFAILED named linking: %d of %d\n',sum(linkstatus == -1),sum(linkstatus ~= 0)) - fprintf('%s\n',DSGN.subjects{linkstatus == -1}) - nfail = nfail + sum(linkstatus == -1); -end -if histstatus == -1, fprintf('\nFAILED t histogram making.\n'); nfail=nfail+1; end -if reviewstatus == -1, fprintf('\nFAILED design reviewing.\n'); nfail=nfail+1; end -if tchkstatus == -1, fprintf('\nFAILED timing check report.\n'); nfail=nfail+1; end - -if ~nfail, fprintf('\nRAN WITH NO PROBLEMS (or at least so it seems).\n'); end - -end - - -% function publish_timing_check(DSGN) -% -% assignin('base','MASK',DSGN.timingcheck.mask); -% assignin('base','OP',DSGN.timingcheck.stat); -% -% beforedir = pwd; -% -% cd(DSGN.modeldir); -% -% outputdir = fullfile(pwd,'timing_check'); -% if exist(outputdir,'dir'), rmdir(outputdir,'s'); end -% mkdir(outputdir); -% -% p = struct('useNewFigure', false, 'maxHeight', 1500, 'maxWidth', 1200, ... -% 'outputDir', outputdir, 'showCode', false); -% -% fout = publish('canlab_glm_subject_levels_timingcheck.m',p); -% fprintf('Created subject level timing check:\n\t%s\n',fout); -% -% cd(beforedir) -% -% end - -%% From Kathy Pearson: -% replace each first instance of SPM-like output backspace with newline; -% ignore additional backspaces found in sequence -% -function [wrapstr] = nobackspace(str) %#ok - -wrapstr = str; -i = strfind(wrapstr, 8); -if ~isempty(i) - k = 0; - n = length(str); - first8 = 1; - for j = 1:n - if str(j) == 8 - if first8 - k = k + 1; - wrapstr(k) = 10; - first8 = 0; - end - else - k = k + 1; - wrapstr(k) = str(j); - first8 = 1; - end - end - wrapstr = wrapstr(1:k); -end - -end - -function parsave(path,DSGN,OPTS,STARTINGDIR) - save(path,'DSGN','OPTS','STARTINGDIR'); -end diff --git a/CanlabCore/GLM_Batch_tools/canlab_glm_subject_levels_run1subject.m b/CanlabCore/GLM_Batch_tools/canlab_glm_subject_levels_run1subject.m index 0f3a783b..a6d769bc 100644 --- a/CanlabCore/GLM_Batch_tools/canlab_glm_subject_levels_run1subject.m +++ b/CanlabCore/GLM_Batch_tools/canlab_glm_subject_levels_run1subject.m @@ -1,10 +1,59 @@ function [modelstatus constatus linkstatus] = canlab_glm_subject_levels_run1subject(wd, s) -% child process of canlab_glm_subject_levels -% (see canlab_glm_README.txt for an overview) +% canlab_glm_subject_levels_run1subject Worker process that runs subject-level GLM for one subject. +% +% :Usage: +% :: +% +% [modelstatus, constatus, linkstatus] = canlab_glm_subject_levels_run1subject(wd, s) +% +% Child process of canlab_glm_subject_levels. Loads the saved +% environment file env_.mat for subject s from the +% working directory wd and runs the model specification / estimation +% job, the contrast job, and the named-link directory creation for that +% single subject. Writes a per-subject diary log +% (diary_.log) into wd. See canlab_glm_README.txt for an +% overview of the canlab_glm_* batch tools. +% +% This function is normally called automatically by +% canlab_glm_subject_levels (including in parallel/cluster modes); it +% is rarely invoked directly by users. % % .. % Copyright (C) 2013 Luka Ruzic % .. +% +% :Inputs: +% +% **wd:** +% Working directory containing the per-subject environment +% file env_.mat saved by canlab_glm_subject_levels. +% Diary logs and status files are also written here. +% +% **s:** +% Integer subject index (1-based). Used to locate +% env_.mat and diary_.log and to index +% DSGN.subjects. +% +% :Outputs: +% +% **modelstatus:** +% Status code for the model specification/estimation step. +% 0 indicates skipped/success; -1 indicates error; positive +% values indicate the step completed. +% +% **constatus:** +% Status code for the contrast generation step (same convention +% as modelstatus). +% +% **linkstatus:** +% Status code for the named-links directory creation step (same +% convention as modelstatus). +% +% :See also: +% - canlab_glm_subject_levels +% - canlab_glm_group_levels +% - canlab_spm_fmri_model_job +% - canlab_spm_contrast_job_luka load(fullfile(wd,sprintf('env_%04d',s))); %% PREP diff --git a/CanlabCore/GLM_Batch_tools/canlab_glm_subject_levels_run1subject_old.m b/CanlabCore/GLM_Batch_tools/canlab_glm_subject_levels_run1subject_old.m deleted file mode 100644 index 7e9b8384..00000000 --- a/CanlabCore/GLM_Batch_tools/canlab_glm_subject_levels_run1subject_old.m +++ /dev/null @@ -1,766 +0,0 @@ -function [modelstatus constatus linkstatus] = canlab_glm_subject_levels_run1subject_old(wd, s) -% child process of canlab_glm_subject_levels -% (see canlab_glm_README.txt for an overview) -% -% .. -% Copyright (C) 2013 Luka Ruzic -% .. - -load(fullfile(wd,sprintf('env_%04d',s))); -%% PREP - -diaryfile = fullfile(wd,sprintf('diary_%04d.log',s)); - -batchname = 'spm_specify_and_estimate_model'; - -if ~isempty(OPTS.parallel_dream) %#ok - z = '> '; -else - z = ''; -end - -% initialize statuses -modelstatus = 0; -constatus = 0; -linkstatus = 0; - -%subject-specific setup -[ignore subnum] = fileparts(DSGN.subjects{s}); %#ok -submodeldir = fullfile(DSGN.modeldir, subnum); % make it later when you know it's not getting skipped -batchfile = fullfile(submodeldir, batchname); - -diary(diaryfile) -fprintf('%s\n%s\n%s\n',z,z,z); -fprintf('%s------------------------------\n',z); -fprintf('%s-- SUBJECT LEVEL ANALYSIS --\n',z); -fprintf('%s------------------------------\n',z); -fprintf('%s\n%sOutput Directory:\n%s\t%s\n',z,z,z,submodeldir); -diary off - - -%% MODEL SPECIFICATION AND ESTIMATION JOBS -diary(diaryfile), fprintf('%s\n%s... MODEL SPECIFICATION AND ESTIMATION JOBS\n',z,z), diary off -if OPTS.onlycons - diary(diaryfile), fprintf('%sSKIPPED: turned off in options.\n',z), diary off -else - run_this_model = true; - - if exist(submodeldir,'dir') - %if ~numel(filenames(fullfile(submodeldir,'beta_*.img'))) - try status = importdata(fullfile(submodeldir,'.ssglm_model_status')); catch, status = ''; end %#ok - if strcmp(status,'started') - diary(diaryfile), fprintf('%sDELETING existing analysis directory: unfinished.\n',z), diary off - rmdir(submodeldir,'s') - elseif numel(filenames(fullfile(submodeldir,'beta_*.img'))) == 0 - diary(diaryfile), fprintf('%sDELETING existing analysis directory: no betas.\n',z), diary off - rmdir(submodeldir,'s') - else - if OP - TS.overwrite - diary(diaryfile), fprintf('%sOVERWRITING: analysis directory exists.\n',z), diary off - rmdir(submodeldir,'s') - else - diary(diaryfile), fprintf('%sSKIPPED: analysis directory exists.\n',z), diary off - run_this_model = false; - end - end - end - - if run_this_model - if ~exist(DSGN.subjects{s},'dir') - diary(diaryfile), fprintf(sprintf('%sERROR: no such data directory: %s\n',z,DSGN.subjects{s})), diary off - modelstatus = -1; - return - end - mkdir(submodeldir); - eval(sprintf('!echo started > %s',fullfile(submodeldir, '.ssglm_model_status'))) % Michael: There seems to be problems for file paths with names - save(fullfile(submodeldir,'DSGN'),'DSGN'); - - %% GET RUNS - diary(diaryfile), fprintf('%sADDING input functional data\n',z), diary off - clear runs runs3d - try - diary(diaryfile) -% Currentcd=pwd; % Ke Edit 07-03-2021 -% cd(DSGN.subjects{s}) - [runs runs3d] = find_runs(DSGN,s,z); - % Ke Edit 07-03-2021 -% cd(Currentcd) - diary off - catch exc - if OPTS.nocatch, cd(STARTINGDIR); rethrow(exc) - else diary(diaryfile), fprintf('> %s\n',getReport(exc,'extended')); diary off; end - modelstatus = -1; - return - end - - diary(diaryfile) - fprintf('Functional data'); - for r = 1:numel(runs), fprintf('%s\t%3d\t%s\n',z,r,runs{r}); end - diary off - - - %% PARSE CONDITIONS, REGRESSORS - diary(diaryfile), fprintf('%sADDING conditions and regressors\n',z), diary off - clear names onsets durations pmods multipleregressors - try - [names onsets durations pmods multipleregressors multipleregressorsbehav] = parse_conditions(DSGN,runs,z); - catch exc - if OPTS.nocatch, cd(STARTINGDIR); rethrow(exc) - else diary(diaryfile), fprintf('> %s\n',getReport(exc,'extended')); diary off; end - modelstatus = -1; - return - end - - %% CONCATENATION (if desired) - vol_cnts = cellfun(@length, runs3d); % tallies number of volumes per scan into a 1 x length(DSGN.concatenation) matrix - vol_cnts = vol_cnts(vol_cnts > 0); -% vol_cnts = vol_cnts(vol_cnts(~cellfun(@isempty,onsets) & ~cellfun(@isempty,durations)) > 0); LVO revert Bogdan's improvement since it breaks LaBGAS firstlevel code - if ~isempty(DSGN.concatenation) - diary(diaryfile), fprintf('%sCONCATENATING data according to DSGN.concatenation:\n',z), diary off - try - diary(diaryfile) - [runs3d names onsets durations pmods multipleregressors] = concatdata(DSGN,submodeldir,runs,runs3d,names,onsets,durations,pmods,multipleregressors,z,multipleregressorsbehav); %#ok - diary off - catch exc - if OPTS.nocatch, cd(STARTINGDIR); rethrow(exc) - else diary(diaryfile), fprintf('> %s\n',getReport(exc,'extended')); diary off; end - modelstatus = -1; - return - end - end - - %% CONVERT CONDITIONS TO SINGLE TRIALS ANALYSIS (if desired) - if DSGN.singletrialsall || isfield(DSGN,'singletrials') - diary(diaryfile), fprintf('%sCONVERTING conditions to single trials analysis (one condition per trial)',z), diary off - try - diary(diaryfile) - [names onsets durations pmods] = convert_to_single_trials(DSGN,names,onsets,durations,pmods); - diary off - catch exc - if OPTS.nocatch, cd(STARTINGDIR); rethrow(exc) - else diary(diaryfile), fprintf('> %s\n',getReport(exc,'extended')); diary off; end - modelstatus = -1; - return - end - end - - %% RUN SPECIFICATION AND ESTIMATION BATCH - % convert into flat arrays + conditions_by_run for canlab_spm_fmri_model_job - clear conditions_by_run - try - diary(diaryfile) - [runs runs3d names onsets durations pmods multipleregressors conditions_by_run OPTS] = prep_for_canlab_spm_fmri_model_job(DSGN,OPTS,runs,runs3d,names,onsets,durations,pmods,multipleregressors,z); %#ok - diary off - catch exc - if OPTS.nocatch, cd(STARTINGDIR); rethrow(exc) - else diary(diaryfile), fprintf('> %s\n',getReport(exc,'extended')); diary off; end - modelstatus = -1; - return - end - -% modeljobcmd = ['matlabbatch = canlab_spm_fmri_model_job(submodeldir, DSGN.tr, DSGN.hpf, runs3d, conditions_by_run, onsets, durations, names, multipleregressors, ''pmod'', pmods ' OPTS.modeljob ', ''vol_cnts'', vol_cnts);']; - % Fixed a bug here that will pass in a nonexistant vol_cnts - % variable if DSGN.concatenation is not set: MS 12/10/2022 - if ~isempty(DSGN.concatenation) - modeljobcmd = ['matlabbatch = canlab_spm_fmri_model_job(submodeldir, DSGN.tr, DSGN.hpf, runs3d, conditions_by_run, onsets, durations, names, multipleregressors, ''pmod'', pmods ' OPTS.modeljob ', ''vol_cnts'', vol_cnts);']; - else - modeljobcmd = ['matlabbatch = canlab_spm_fmri_model_job(submodeldir, DSGN.tr, DSGN.hpf, runs3d, conditions_by_run, onsets, durations, names, multipleregressors, ''pmod'', pmods ' OPTS.modeljob ');']; - end - - diary(diaryfile), fprintf('%s\nRUNNING model\n\t%s\n',modeljobcmd,z), diary off - try - eval(modeljobcmd); - save(batchfile, 'matlabbatch'); - - spm_jobman('run', matlabbatch); - % eval(sprintf('!echo finished > %s',"'",fullfile(submodeldir, - % '.ssglm_model_status'),"'")) - % Michael: This fix is for if there're spaces in in the path - % lukasvo76: commented out, did not work and is not needed - modelstatus = 1; - catch exc - if OPTS.nocatch, cd(STARTINGDIR); rethrow(exc) - else diary(diaryfile), fprintf('> %s\n',getReport(exc,'extended')); diary off; end - modelstatus = -1; - return - end - end - close all -end - - - -%% CONTRAST JOB -diary(diaryfile), fprintf('%s\n%s... CONTRAST JOB.\n',z,z), diary off -try status = importdata(fullfile(submodeldir,'.ssglm_contrast_status')); catch, status = ''; end %#ok -if ~isfield(DSGN,'contrasts') || ~numel(DSGN.contrasts)~=0 - diary(diaryfile), fprintf('%sSKIPPED: no contrasts specified.\n',z), diary off -else - if modelstatus ~= 1 % otherwise the model ran and must try to run cons - if modelstatus == -1 - diary(diaryfile), fprintf('%sSKIPPED: model failed.\n',z), diary off - return - elseif ~OPTS.onlycons && numel(filenames(fullfile(submodeldir, 'spmT_*.img'))) == numel(DSGN.contrasts) && ~strcmp(status,'started') - diary(diaryfile), fprintf('%sSKIPPED: already previously run, (and onlycons is not set).\n',z), diary off - return - end - end - - conjobcmd = ['canlab_spm_contrast_job_luka(submodeldir, DSGN.contrasts, ''names'', DSGN.contrastnames, ''weights'', DSGN.contrastweights' OPTS.conjob ');']; - diary(diaryfile), disp(conjobcmd), diary off - try - eval(sprintf('!echo started > %s',fullfile(submodeldir,'.ssglm_contrast_status'))) - eval(conjobcmd); - eval(sprintf('!echo finished > %s',fullfile(submodeldir,'.ssglm_contrast_status'))) - constatus = 1; - catch exc - if OPTS.nocatch, cd(STARTINGDIR); rethrow(exc) - else diary(diaryfile), fprintf('> %s\n',getReport(exc,'extended')); diary off; end - constatus = -1; - return - end -end - - - -%% MAKE NAMED LINKS TO T MAPS -if OPTS.run_renaming && constatus==1 - diary(diaryfile), fprintf('%s\n%s... MAKING named links to spmT maps.\n',z,z), diary off - try - link_to_stat_maps(submodeldir) - linkstatus = 1; - catch exc - if OPTS.nocatch, cd(STARTINGDIR); rethrow(exc) - else diary(diaryfile), fprintf('> %s\n',getReport(exc,'extended')); diary off; end - linkstatus = -1; - end -end - - -end - - -% ------------------------------------------------------------------------- -% SUBFUNCTIONS ---------------------------------------------------------- -% ------------------------------------------------------------------------- - - -%% -function [runs runs3d] = find_runs(DSGN,session,z) - -r=1; -for f = 1:numel(DSGN.funcnames) - % find runs using path from funcnames (wildcards optional) - runstoadd = filenames(fullfile(DSGN.subjects{session}, DSGN.funcnames{f})); - - - % runstoadd = filenames(DSGN.funcnames{f}); %% Ke Bo Edit, we had some directory problems. - if isempty(runstoadd) - if DSGN.allowmissingfunc - fprintf('%sWARNING: no runs found with: %s\n',z,DSGN.funcnames{f}) - runs{r} = ''; %#ok - runs3d{r} = ''; %#ok - r=r+1; - continue - else - error('%sno runs found with: %s\n\t(to allow, set DSGN.allowmissingfunc)',z,DSGN.funcnames{f}) - end - end - - % expand - for j = 1:numel(runstoadd) - runs{r} = runstoadd{j}; %#ok - runs3d{r} = cellstr(expand_4d_filenames(runs{r})); %#ok - r=r+1; - end -end - -end - - -%% -function [names onsets durations pmods multipleregressors multipleregressorsbehav] = parse_conditions(DSGN,runs,z) - -newpmod = struct('name', [], 'param', [], 'poly', []); -emptypmod = newpmod([]); - -for session = 1:numel(runs) - % skip empty runs - if isempty(runs{session}), continue; end - - % if only first session described, use for all sessions - if size(DSGN.conditions,2) == 1 - sess = 1; - else - sess = session; - end - - % specify modeling files directory - [funcdir] = fileparts(runs{session}); - mfdir = fullfile(funcdir, DSGN.modelingfilesdir); - - c = 1; % keeps track of condition number - - for i = 1:numel(DSGN.conditions{sess}) - clear matfiles - - if ~isempty(regexp(DSGN.conditions{sess}{i},'[[]{}*?]','once')) - wc = fullfile(mfdir,DSGN.conditions{sess}{i}); - matfiles = filenames(wc); - if isempty(matfiles) - if DSGN.allowmissingcondfiles - fprintf('%sWARNING: No conditions files found with wildcard: %s\n',z,wc) - continue - else - error('%sNo conditions files found with wildcard: %s\n (See DSGN.allowmissingcondfiles)',z,wc) - end - end - else - matfiles{1} = fullfile(mfdir,DSGN.conditions{sess}{i}); -% matfiles{1} = fullfile(mfdir,'..','..',DSGN.conditions{sess}{i}); - matfiles{1} = [regexprep(matfiles{1},'.mat$','') '.mat']; - if ~exist(matfiles{1},'file') - if DSGN.allowmissingcondfiles - fprintf('%sWARNING: No such conditions file: %s\n',z,matfiles{1}) - continue - else - error('%sNo such conditions file: %s\n (See DSGN.allowmissingcondfiles)',z,matfiles{1}) - end - end - end - - % get pmods - if isfield(DSGN,'pmods') - try - currpmods = emptypmod; - for j = 1:numel(DSGN.pmods{sess}{i}) - matfile = fullfile(mfdir,DSGN.pmods{sess}{i}{j}); - matfile = [regexprep(matfile,'.mat$','') '.mat']; - if ~exist(matfile,'file'), error('%sNo such pmods file: %s',z,matfile); end - pmodinfo = load(matfile); - - for k = 1:numel(pmodinfo.name) - l = size(currpmods,2) + 1; - % initialize - currpmods(l) = newpmod; - currpmods(l).name = pmodinfo.name{k}; - currpmods(l).param = pmodinfo.param{k}; - currpmods(l).poly = pmodinfo.poly{k}; - end - end - catch %#ok - currpmods = emptypmod; - end - else - currpmods = emptypmod; - end - - for m = 1:numel(matfiles) - condinfo = load(matfiles{m}); - - % loop through all conditions in mat - for j = 1:numel(condinfo.name) - if ~numel(condinfo.onset{j}) - names{session}{c} = condinfo.name{j}; %#ok - onsets{session}{c} = []; %#ok - durations{session}{c} = []; %#ok - pmods{session}{c} = emptypmod; %#ok - else - % load name - names{session}{c} = condinfo.name{j}; %#ok - - % load onsets - if size(condinfo.onset{j},1) == 1 && size(condinfo.onset{j},2) > 1 - condinfo.onset{j} = condinfo.onset{j}'; - elseif size(condinfo.onset{j},1) > 1 && size(condinfo.onset{j},2) > 1 - error('onsets field in %s stored as matrix (must be scalar or vector)',matfiles{m}); - end - onsets{session}{c} = condinfo.onset{j}; %#ok - - % load durations - if size(condinfo.duration{j},1) == 1 && size(condinfo.duration{j},2) > 1 - condinfo.duration{j} = condinfo.duration{j}'; - elseif size(condinfo.duration{j},1) > 1 && size(condinfo.duration{j},2) > 1 - error('durations field in %s stored as matrix (must be scalar or vector)',matfiles{m}); - end - durations{session}{c} = condinfo.duration{j}; %#ok - - % add currently indicated pmods - pmods{session}{c} = currpmods; %#ok - - % load pmods from condition file - if isfield(condinfo,'pmod') && ~isempty(condinfo.pmod) - for k = 1:numel(condinfo.pmod.name) - l = size(pmods{session}{c},2) + 1; - pmods{session}{c}(l) = newpmod; %#ok - pmods{session}{c}(l).name = condinfo.pmod.name{k}; %#ok - pmods{session}{c}(l).param = condinfo.pmod.param{k}; %#ok - pmods{session}{c}(l).poly = condinfo.pmod.poly{k}; %#ok - end - end - end - c=c+1; - end - end - - end - - % retrieve multiple regressors file - if ~isempty(DSGN.multireg) - multiregfile = fullfile(mfdir, DSGN.multireg); % Michael and Ke: -% We are changing this line to better deal with the BIDS formatting -% that does not feature separate run folders -% multiregfile = fullfile(mfdir, DSGN.multireg{session}{1}); -% if ~exist(multiregfile,'file'), error('> No such multiple regressors file : %s',multiregfile); end - multipleregressors{session} = multiregfile; %#ok - else - multipleregressors{session} = {}; %#ok - end - - % retrieve behavioral multiple regressors file - if ~isempty(DSGN.multiregbehav) - multiregbehavfile = fullfile(mfdir, DSGN.multiregbehav); -% if ~exist(multiregfile,'file'), error('> No such multiple regressors file : %s',multiregfile); end - multipleregressorsbehav{session} = multiregbehavfile; %#ok - else - multipleregressorsbehav{session} = {}; %#ok - end -end -end - - -%% -% Bogdan: in this script it looks like 'oldsess' refers to runs while -% 'sess' refers to sessions. Maybe the variable names could be updated to -% be more intuitive? -function [runs3d names onsets durations pmods multipleregressors] = concatdata(DSGN,submodeldir,oldruns,oldruns3d,oldnames,oldonsets,olddurations,oldpmods,oldmultipleregressors,z,oldmultipleregressorsbehav) - - -emptyruns = find(cellfun('isempty',oldruns)); -% emptyons = find(~ismember(1:length(oldruns), find(~cellfun(@isempty, oldonsets)))); % LVO reverted Bogdan's improvement again -% emptydurs = find(~ismember(1:length(oldruns), find(~cellfun(@isempty, olddurations)))); -% emptyruns = unique([emptyruns, emptyons, emptydurs]); -if DSGN.allowmissingfunc && ~isempty(emptyruns) - i=1; - for c = 1:numel(DSGN.concatenation) - concat{i} = []; %#ok - for r = 1:numel(DSGN.concatenation{c}) - if ~any(DSGN.concatenation{c}(r) == emptyruns) - concat{i} = [concat{i} DSGN.concatenation{c}(r)]; %#ok - end - end - if (numel(concat{i}) > 1), i=i+1; end - end -else - concat = DSGN.concatenation; -end - -for i = 1:numel(concat) - fprintf('%s\tsession %d is run(s):',z,i); - for j = 1:numel(concat{i}) - fprintf(' %3d', concat{i}(j)); - end - fprintf('\n'); -end - -% initialize -runs3d = {}; -names = {}; -onsets = {}; -durations = {}; -pmods = {}; -multipleregressors = ''; - -for sess = 1:numel(concat) - oldsess1 = concat{sess}(1); - - % concatenate functional data - runs3d{sess} = []; %#ok - for r = 1:numel(concat{sess}) - oldsess = concat{sess}(r); - runs3d{sess} = [runs3d{sess}; cellstr(expand_4d_filenames(oldruns{oldsess}))]; %#ok - end - - starttime = 0; - for r = 1:numel(concat{sess}) - oldsess = concat{sess}(r); - for cond = 1:numel(oldonsets{oldsess}) - % names - if r==1 - names{sess}{cond} = oldnames{oldsess}{cond}; %#ok - elseif ~strcmp(names{sess}{cond},oldnames{oldsess}{cond}) - error(['Inconsistent names for condition ' num2str(cond) ' across sessions (e.g., ' names{sess}{cond} ', ' oldnames{oldsess}{cond} ')']) - end - - % onsets - if r==1, onsets{sess}{cond} = []; end %#ok - onsets{sess}{cond} = [onsets{sess}{cond}; oldonsets{oldsess}{cond} + starttime]; %#ok - - % durations - if r==1, durations{sess}{cond} = []; end %#ok - if numel(olddurations{oldsess}{cond}) == 1 - % extend single duration across all onsets (in case single duration is different across runs being concatenated) - olddurations{oldsess}{cond} = repmat(olddurations{oldsess}{cond},size(oldonsets{oldsess}{cond},1),1); - end - durations{sess}{cond} = [durations{sess}{cond}; olddurations{oldsess}{cond}]; %#ok - - % pmods - if ~numel(oldpmods{oldsess}{cond}) - % if this is run 2 and this condition is missing on run 2 - % then we enter this conditional, but what if it wasn't - % missing on run 1? Then we're overwriting that data. Let's - % fix that - %pmods{sess}{cond} = oldpmods{oldsess}{cond}; %#ok - if length(pmods) < sess || length(pmods{sess}) < cond % if pmods{sess}{cond} doesn't exist - pmods{sess}{cond} = oldpmods{oldsess}{cond}; %#ok - elseif isempty(pmods{sess}{cond}) % if it exists but is empty. Doubt this will ever occur, but just in case - pmods{sess}{cond} = oldpmods{oldsess}{cond}; %#ok - end - else - for p = 1:size(oldpmods{oldsess}{cond},2) - if r==1 - pmods{sess}{cond}(p).poly = oldpmods{oldsess1}{cond}(p).poly; %#ok - pmods{sess}{cond}(p).name = oldpmods{oldsess1}{cond}(p).name; %#ok - pmods{sess}{cond}(p).param = []; %#ok - end - - % Bogdan: pmods is always empty on r == 1 but it can be - % empty on subsequent runs if r == 1 was missing 'this' - % condition, so let's make this conditional to account - % for that too. - if numel(pmods{sess}{cond}) < p - pmods{sess}{cond}(p).poly = oldpmods{oldsess}{cond}(p).poly; %note use of oldsess, not oldsess1 - pmods{sess}{cond}(p).name = oldpmods{oldsess}{cond}(p).name; - pmods{sess}{cond}(p).param = oldpmods{oldsess}{cond}(p).param(:); % note non-null assignment - else - pmods{sess}{cond}(p).param = [pmods{sess}{cond}(p).param(:); oldpmods{oldsess}{cond}(p).param(:)]; %#ok - end - end - end - end - starttime = starttime + (DSGN.tr * numel(oldruns3d{oldsess})); - end - - % concatenate regressors - multipleregressors{sess} = fullfile(submodeldir,sprintf('multireg_%d.mat',sess)); - newR = []; - newRbehav = []; - cri = {}; - for r = 1:numel(concat{sess}) - oldsess = concat{sess}(r); - if ~isempty(oldmultipleregressors{oldsess}) - load(oldmultipleregressors{oldsess}); - oldR = R; - else - oldR=[]; - end - if ~isempty(oldmultipleregressorsbehav{oldsess}) - load(oldmultipleregressorsbehav{oldsess}); - oldRbehav = R2; - else - oldRbehav=[]; - end - if ~isfield(DSGN,'customrunintercepts') - % add intercept (ignore first one) if we don't have - % fmri_concatenate support available. If we do that will - % account for run intercepts and other things. A warning is - % thrown by canlab_spm_fmri_model_job if we'r emissing it. - if exist([cell2mat(spm_get_defaults('tbx.dir')), '/CANLab_spm_toolbox/'],'dir') ~= 7 && r > 1 - oldR(:,end+1) = 1; %#ok - end - else - % initialize - if isempty(oldR) - tmpn = nifti(oldruns{oldsess}); - cri{r} = zeros(size(tmpn.dat,4),numel(DSGN.customrunintercepts)); %#ok - else - cri{r} = zeros(size(oldR,1),numel(DSGN.customrunintercepts)); %#ok - end - - for i = 1:numel(DSGN.customrunintercepts) - if any(oldsess == DSGN.customrunintercepts{i}) - cri{r}(:,i) = 1; %#ok - end - end - end - % add linear trend - oldR(:,end+1) = scale([1:size(oldR,1)]'); %#ok - - % append to growing block diagonal nuisance matrix - newR = blkdiag(newR,oldR); - newRbehav = [newRbehav; oldRbehav]; - end - R = [newRbehav newR vertcat(cri{:})]; - save(multipleregressors{sess}, 'R'); -end - -% add rest of stuff -catruns = cell2mat(concat); -for r = 1:numel(oldruns) -% if ~any(catruns == r) && ~ismember(r, emptyruns) % LVO again reverting Bogdan's improvement - if ~any(catruns == r) && ~isempty(oldruns{r}) - fprintf ('%s\tsession %d is run(s): %3d\n',z,numel(runs3d)+1,r) - runs3d{end+1} = oldruns3d{r}; %#ok - names{end+1} = oldnames{r}; %#ok - onsets{end+1} = oldonsets{r}; %#ok - durations{end+1} = olddurations{r}; %#ok - pmods{end+1} = oldpmods{r}; %#ok - multipleregressors{end+1} = oldmultipleregressors{r}; %#ok - multipleregressorsbehav{end+1} = oldmultipleregressorsbehav{r}; %#ok - end -end - -end - - -%% -function [names onsets durations pmods] = convert_to_single_trials(DSGN,oldnames,oldonsets,olddurations,oldpmods) - -newpmod = struct('name', [], 'param', [], 'poly', []); -emptypmod = newpmod([]); - -if isfield(DSGN,'singletrials') && numel(DSGN.singletrials) == 1 - for s = 2:numel(oldnames) - DSGN.singletrials{s} = DSGN.singletrials{1}; - end -end - -for s = 1:numel(oldonsets) - o = 1; - for c = 1:numel(oldonsets{s}) - if numel(olddurations{s}{c})==1 - olddurations{s}{c} = repmat(olddurations{s}{c},numel(oldonsets{s}{c}),1); - end - % note: look for better way to deal with sparse cell array! - try - thiscond = logical(DSGN.singletrials{s}{c}); - if isempty(DSGN.singletrials{s}{c}), DSGN.singletrials{s}{c} = false; end - catch %#ok - thiscond = false; - end - - if DSGN.singletrialsall || thiscond - if sum(size(oldpmods{s}{c})==[0 0])~=2 - error('Sorry, single trials option is not currently compatible with pmods') - end - for t = 1:numel(oldonsets{s}{c}) - names{s}{o} = sprintf('%s_trial%04d',oldnames{s}{c},t); %#ok - onsets{s}{o} = oldonsets{s}{c}(t); %#ok - durations{s}{o} = olddurations{s}{c}(t); %#ok - pmods{s}{o} = emptypmod; %#ok - o=o+1; - end - else - names{s}{o} = oldnames{s}{c}; %#ok - onsets{s}{o} = oldonsets{s}{c}; %#ok - durations{s}{o} = olddurations{s}{c}; %#ok - pmods{s}{o} = oldpmods{s}{c}; %#ok - o=o+1; - end - end -end - -end - - -%% -function [nonemptyruns nonemptyruns3d flatnames flatonsets flatdurations flatpmods flatmultipleregressors conditions_by_run OPTS] = prep_for_canlab_spm_fmri_model_job(DSGN,OPTS,runs,runs3d,names,onsets,durations,pmods,multipleregressors,z) - -nonemptyruns = {}; -nonemptyruns3d = {}; -for i=1:numel(runs3d) - if ~isempty(runs3d{i}) - nonemptyruns{end+1} = runs{i}; %#ok - nonemptyruns3d{end+1} = runs3d{i}; %#ok - end -end - -flatnames = {}; -flatonsets = {}; -flatdurations = {}; -flatpmods = {}; -conditions_by_run = []; -newsess = 0; -for session = find(~cellfun('isempty',runs3d)) %1:numel(names) - newsess = newsess+1; - i=0; - for cond = 1:numel(names{session}) - if isempty(onsets{session}{cond}) - if DSGN.allowemptycond - fprintf('%sWARNING: no onsets in session %d, condition %d: %s\n',z,session,cond,names{session}{cond}) - continue - else - error('no onsets in session %d, condition %d: %s\n\t(to allow, set DSGN.allowemptycond)',session,cond,names{session}{cond}) - end - end - flatnames{end+1} = [names{session}{cond} ' ']; %#ok % space added to separate out tmods, pmods, basis functions, etc - flatonsets{end+1} = onsets{session}{cond}; %#ok - flatdurations{end+1} = durations{session}{cond}; %#ok - flatpmods{end+1} = pmods{session}{cond}; %#ok - i=i+1; - end - conditions_by_run(newsess) = i; %#ok - flatmultipleregressors(newsess) = multipleregressors(session); % added by lukasvo Feb 2021 to fix problem with sessions indices of multipleregressors versus onsets, durations etc in case of non-concatenation and non-final missing runs, downstream in canlab_spm_fmri_model_job -end - -switch DSGN.convolution.type - case 'hrf' - OPTS.modeljob = [OPTS.modeljob ',' '''hrf''' ',' num2str(DSGN.convolution.time) ',' num2str(DSGN.convolution.dispersion)]; - case 'fir' - OPTS.modeljob = [OPTS.modeljob ',' '''fir''' ',' num2str(DSGN.convolution.windowlength) ',' num2str(DSGN.convolution.order)]; - if ~isfield(DSGN.convolution,'keepdurations') || ~DSGN.convolution.keepdurations - % zero-out durations - for c = 1:numel(flatdurations) - flatdurations{c} = 0; %#ok - end - end - case 'spline' - % requires SPM spline patch - OPTS.modeljob = [OPTS.modeljob ',' '''spline''' ',' ... - num2str(DSGN.convolution.windowlength) ',' num2str(DSGN.convolution.order), ',', num2str(DSGN.convolution.degree)]; - otherwise - error('Unrecognized convolution type: %s',DSGN.convolution.type) -end - -if DSGN.ar1 - OPTS.modeljob = [OPTS.modeljob ',''AR(1)''']; -end - -if DSGN.fast - OPTS.modeljob = [OPTS.modeljob ',''FAST''']; -end - -end - - -%% -function link_to_stat_maps(targdir) - -startingdir = pwd; - -cd(targdir) -load('SPM.mat') -% load('contrastnames.mat') - -renamedir = fullfile(pwd, 'named_statmaps'); -if exist(renamedir,'dir'), rmdir(renamedir,'s'); end -mkdir(renamedir); - -for i=1:numel(SPM.xCon) - mapname = SPM.xCon(i).name; - % clean up the name for filename friendliness - mapname = regexprep(mapname,'[()]',''); - mapname = regexprep(mapname,'[^0-9A-Za-z-_]','_'); - for stat = {'spmT' 'con'} - for ext = {'img' 'hdr'} - imgname = fullfile(pwd, sprintf('%s_%04d.%s',stat{1},i,ext{1})); - linkname = fullfile(renamedir, [stat{1} '_' mapname '.' ext{1}]); - eval(['!ln -v -s ' imgname ' ' linkname]); % lukasvo: this does not work on a Windows system as the Linux command ln is not recognized as internal or external command on Windows - does not cause problems or errors otherwise - end - end -end - -cd(startingdir) - -end diff --git a/CanlabCore/GLM_Batch_tools/canlab_prep_bidsdir.m b/CanlabCore/GLM_Batch_tools/canlab_prep_bidsdir.m index 1356aea1..cbe9d778 100644 --- a/CanlabCore/GLM_Batch_tools/canlab_prep_bidsdir.m +++ b/CanlabCore/GLM_Batch_tools/canlab_prep_bidsdir.m @@ -1,52 +1,88 @@ function canlab_prep_bidsdir(bidsdir, varargin) - % CANLAB_PREP_BIDSDIR Convert a BIDS directory for canlab_glm_subject_levels compatibility - % - % This function 'canlabulates' a directory, making it compatible for - % canlab_glm_subject_levels first-level analyses. It creates symbolic links - % instead of copying files, ensuring no redundant space usage while keeping - % the original BIDS directory intact. Optionally, you can create a BIDS - % structure outside the original BIDS directory by specifying 'outdir'. - % - % Usage: - % canlab_prep_bidsdir(bidsdir, 'datatype', datatypestring, 'noise', noisestring, 'outdir', outdir) - % - % Examples: - % Clone T1 for CAT12 analysis: - % canlab_prep_bidsdir(raw_bidsdir, 'datatype', anat, 'outdir', outdir) - % - % Clone funcs for CANlab First-Level Analyses: - % canlab_prep_bidsdir(fmriprep_bidsdir, 'datatype', func, 'taskdir', raw_bidsdir, 'outdir', outdir) - % - % Required Argument: - % bidsdir - Full filepath to the BIDS-directory of your study. - % e.g., 'F:\Dropbox (Dartmouth College)\Tor Pinel datasets\Pinel_localizer\data\pinel_localizer_Dartmouth_S2019' - % - % Optional Key-Value Arguments: - % 'datatype' - Data type ('func' or 'anat') filename identifiers (with wildcards) to pull from - % every subject. Default: 'func'. - % 'filename' - The filename identifiers (with wildcards) based on datatype. - % Default: '*space-MNI152NLin2009cAsym_desc-preproc_bold.nii.gz' for 'func' and *T1w.nii.gz' for 'anat'. - % - % 'noise' - Confound filename identifiers (with wildcards) to pull from - % every subject. Default: '*desc-confounds_timeseries.tsv'. - % - % 'taskdir' - Full filepath of the root BIDS directory where the task files reside. - % By default, it's the same as the input BIDS - % directory, but more likely than not, it is the root - % BIDS directory (non-derivative). - % - % 'task' - Task filename identifiers (with wildcards) to pull from - % every subject. This likely has to pull from the original BIDS directory so set that. Default: '*_events.tsv'. - % - % 'outdir' - Full filepath of the directory where the new structure with symbolic links will be created. - % By default, it's the same as the input BIDS directory. - % - % Notes: - % - For symbolic link creation on Windows, you must run Matlab as administrator. - % - Always ensure you have backup copies of your data before performing directory operations. - % - % Function by Michael Sun, Ph.D. 11/29/2022 - % Updated: 10/19/2023 + % canlab_prep_bidsdir Convert a BIDS directory for canlab_glm_subject_levels compatibility. + % + % :Usage: + % :: + % + % canlab_prep_bidsdir(bidsdir, 'datatype', datatypestring, ... + % 'noise', noisestring, 'outdir', outdir) + % + % This function 'canlabulates' a directory, making it compatible + % for canlab_glm_subject_levels first-level analyses. It creates + % symbolic links instead of copying files, ensuring no redundant + % space usage while keeping the original BIDS directory intact. + % Optionally, you can create a BIDS structure outside the original + % BIDS directory by specifying 'outdir'. + % + % :Notes: + % - For symbolic link creation on Windows, you must run MATLAB + % as administrator. + % - Always ensure you have backup copies of your data before + % performing directory operations. + % + % Function by Michael Sun, Ph.D. 11/29/2022. Updated: 10/19/2023. + % + % :Inputs: + % + % **bidsdir:** + % Full filepath to the BIDS directory of your study, e.g., + % 'F:\Dropbox (Dartmouth College)\Tor Pinel datasets\ + % Pinel_localizer\data\pinel_localizer_Dartmouth_S2019'. + % + % :Optional Inputs: + % + % **'datatype':** + % Data type ('func' or 'anat') filename identifiers (with + % wildcards) to pull from every subject. Default: 'func'. + % + % **'filename':** + % The filename identifiers (with wildcards) based on + % datatype. Default: + % '*space-MNI152NLin2009cAsym_desc-preproc_bold.nii.gz' for + % 'func' and '*T1w.nii.gz' for 'anat'. + % + % **'noise':** + % Confound filename identifiers (with wildcards) to pull + % from every subject. Default: + % '*desc-confounds_timeseries.tsv'. + % + % **'taskdir':** + % Full filepath of the root BIDS directory where the task + % files reside. By default, it's the same as the input BIDS + % directory, but more likely than not, it is the root BIDS + % directory (non-derivative). + % + % **'task':** + % Task filename identifiers (with wildcards) to pull from + % every subject. This likely has to pull from the original + % BIDS directory, so set that. Default: '*_events.tsv'. + % + % **'outdir':** + % Full filepath of the directory where the new structure + % with symbolic links will be created. By default, it's the + % same as the input BIDS directory. + % + % :Outputs: + % + % None returned to the workspace. Side effects: creates a + % directory tree under outdir containing symbolic links to + % the requested data, noise, and task files (with one + % subdirectory per BIDS run for func data). + % + % :Examples: + % :: + % + % % Clone T1 for CAT12 analysis + % canlab_prep_bidsdir(raw_bidsdir, 'datatype', 'anat', ... + % 'outdir', outdir); + % + % % Clone funcs for CANlab first-level analyses + % canlab_prep_bidsdir(fmriprep_bidsdir, 'datatype', 'func', ... + % 'taskdir', raw_bidsdir, 'outdir', outdir); + % + % :See also: + % - canlab_glm_subject_levels + % - canlab_glm_group_levels % Create an input parser object p = inputParser; diff --git a/CanlabCore/HRF_Est_Toolbox2/Anneal_Logit.m b/CanlabCore/HRF_Est_Toolbox2/Anneal_Logit.m deleted file mode 100644 index cfd2365d..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/Anneal_Logit.m +++ /dev/null @@ -1,127 +0,0 @@ -function [theta,HH,C,P,hrf,fit,e,param] = Anneal_Logit(theta0,t,tc,Run) -% -% [theta,HH,C,P] = Anneal_Logit(theta0,t,tc,Run) -% -% Estimate inverse logit (IL) HRF model using Simulated Annealing -% Creates fitted curve - 3 logistic functions to be summed together - from parameter estimates -% -% INPUT: theta0, t, tc, Run -% Run = stick function -% tc = time course -% t = vector of time points -% theta0 = initial value for the parameter vector -% -% By Martin Lindquist and Tor Wager -% Edited 12/12/06 -% - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% -% Initial values - -iter = 15000; % Number of iterations -theta = theta0; % Set initial value for the parameter vector -h0 = cost(theta0,t,tc,Run); % Calculate cost of initial estimate -LB = [0.05, 1, 0, 0.05, 5, 0, 10]; % Lower bounds for parameters -UB = [10, 15, 5, 10, 15, 5, 30]; % Upper bounds for parameters - -% -% These values may need tweaking depending on the individual situation. -% - -r1= 0.001; % A parameters -r1b= 0.001; % A parameters -r2 = 0.05; % T parameters -r3 = 0.001; % delta parameters - -t1 = [1 4]; -t1b = [6]; -t2 = [2 5 7]; -t3 = [3]; - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -u = zeros(1,7); - -HH = zeros(1+iter,7); % Keep track of theta_i -HH(1,:) = theta0; -P = zeros(1+iter,1); -C = zeros(1+iter,1); % Keep track of the cost function -C(1) = h0; - -cnt = 0; -for i=1:iter, - - T = 100/log(1+i); %Temperature function (may require tweaking) - th = zeros(1,7); - ind = 0; - - % Choose a new candidate solution theta_{i+1}, based on a random perturbation of the current solution of theta_{i}. - % Check new parameters are within accepted bounds - while ( (sum((LB-th)>0) + sum((th-UB)>0)) > 0), - - % Perturb solution - - u(t1) = normrnd(0,r1,1,2); - u(t1b) = normrnd(0,r1b,1,1); - u(t2) = normrnd(0,r2,1,3); - u(t3) = normrnd(0,r3,1,1); - - % Update solution - th = theta + u; - ind = ind + 1; - - if(ind > 500), - warning('stuck!'); - return; - end; - end; - - h = cost(th,t,tc,Run); - C(i+1) = h; - delta = h - h0; - - % Determine whether to update the parameter vector. - if (unifrnd(0,1) < min(exp(-delta/T),1)), - theta = th; - h0=h; - cnt = cnt+1; - end; - - HH(i+1,:) = theta; - P(i+1) = min(exp(-delta/T),1); - -end; - -%cnt/iter - -[a,b] = min(C); -theta = HH(b,:); -%h - - -% Additional outputs -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Get HRF for final model -if nargout > 4 - hrf = Get_Logit(theta(1:7),t); % Calculate HRF estimate (fit, given theta) -end - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Convolve HRF and stick function -if nargout > 5 - len = length(Run); - fit = conv(Run, hrf); - fit = fit(1:len); - e = tc - fit; -end - -if nargout > 7 -% [param] = get_parameters_logit(hrf,t,theta); - param = get_parameters2(hrf,t); -end - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - - -return diff --git a/CanlabCore/HRF_Est_Toolbox2/Det_Logit.m b/CanlabCore/HRF_Est_Toolbox2/Det_Logit.m deleted file mode 100644 index 06c91779..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/Det_Logit.m +++ /dev/null @@ -1,116 +0,0 @@ -function [VM, hrf, fit, e, param] = Det_Logit(V0,t,tc,Run) -% -% [VM, h, fit, e, param] = Det_Logit_allstim(V0,t,tc,Run) -% -% Estimate inverse logit (IL) HRF model -% Creates fitted curve - 3 logistic functions to be summed together - from parameter estimates -% -% INPUT: V0, t, tc, Run -% Run = stick function -% tc = time course -% t = vector of time points -% V0 = initial value for the parameter vector -% -% By Martin Lindquist, Christian Waugh and Tor Wager -% Created by Martin Lindquist on 10/02/09 -% Last edited: 05/26/10 (ML) - - -numstim = length(Run); -len = length(Run{1}); - -% LB = [0.05, 1, 0, 0.05, 4, 0, 10]; % Lower bounds for parameters -% UB = [10, 15, 10, 10, 15, 5, 50]; % Upper bounds for parameters -% LB = repmat(LB, 1, numstim); -% UB = repmat(UB, 1, numstim); - -% Remove intercept - -%b0 = pinv(ones(length(tc),1))*tc; -%tc = tc - b0; - -% Find optimal values - -options = optimset('MaxFunEvals',10000,'Maxiter',10000,'TolX',1e-6,'TolFun',1e-6,'Display','off'); - -%VM = fminsearchbnd(@cost_allstim, V0, LB,UB,options,t,tc,Run); -VM = fminsearch(@msq_logit,V0,options,Run,t,tc); - -% Use optimal values to fit hemodynamic response functions -hrf =zeros(length(t),numstim); -fitt = zeros(len,numstim); -param = zeros(3,numstim); - -for g = 1:numstim - hrf(:,g) = il_hdmf_tw2(t,VM(((g-1)*7+1):(g*7))); % Calculate HRF estimate (fit, given theta) - param(:,g) = get_parameters2(hrf(:,g),t); - fits(:,g) = conv(Run{g}, hrf(:,g)); - fitt(:,g) = fits(1:len,g); -end - -fit = sum(fitt,2); -e = tc-fit; -%fit = fit + b0; - -end - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% -% SUBFUNCTIONS -% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -function m=msq_logit(V,Run, t, tc) - -numstim = length(Run); -len = length(Run{1}); -h = zeros(length(t),numstim); -yhatt =zeros(len,numstim); - -for k = 1:numstim - h(:,k) = il_hdmf_tw2(t,V(((k-1)*7+1):(k*7))); % Get IL model corresponding to parameters V - yhat(:,k) = conv(Run{k}, h(:,k)); % Convolve IL model with stick function - yhatt(:,k) = yhat(1:len,k); -end - -yhat2 = sum(yhatt,2); %Sum models together to get overall estimate - -m = sum((tc-yhat2).^2); % Calculate cost function - -end - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -function [h,base] = il_hdmf_tw2(t,V) -% inverse logit -- creates fitted curve from parameter estimates -% -% t = vector of time points -% V = parameters - -% 3 logistic functions to be summed together -base = zeros(length(t),3); -A1 = V(1); -T1 = V(2); -d1 = V(3); -A2 = V(4); -T2 = V(5); -A3 = V(6); -T3 = V(7); -d2 = -d1*(ilogit(A1*(1-T1)) - ilogit(A3*(1-T3)))/(ilogit(A2*(1-T2)) + ilogit(A3*(1-T3))); -d3 = abs(d2)-abs(d1); - -base(:,1)= d1*ilogit(A1*(t-T1))'; -base(:,2)= d2*ilogit(A2*(t-T2))'; -base(:,3)= d3*ilogit(A3*(t-T3))'; -h = sum(base,2)'; - -end - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -function [L] = ilogit(t) -L = exp(t)./(1+exp(t)); -end - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/EstHRF_inAtlas Tutorial.mlx b/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/EstHRF_inAtlas Tutorial.mlx deleted file mode 100644 index d0f9051c..00000000 Binary files a/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/EstHRF_inAtlas Tutorial.mlx and /dev/null differ diff --git a/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/EstimateHRF_inAtlas.m b/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/EstimateHRF_inAtlas.m deleted file mode 100644 index 6952f750..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/EstimateHRF_inAtlas.m +++ /dev/null @@ -1,722 +0,0 @@ -function [tc, HRF, HRF_OBJ, PARAM_OBJ]=EstimateHRF_inAtlas(fmri_d, PREPROC_PARAMS, HRF_PARAMS, at, rois, outfile) - % EstimateHRF_inAtlas takes a raw 4D fmri_data object, preprocesses it, and - % then outputs an estimated HRF time series for each condition of interest. - % PREPROC struct needs to have TR, hpf, and Condition information - % HRF struct is a structure of HRF fitting parameters. It needs to have T, FWHM, alpha, and type, otherwise default - % values will be supplied. - % - % Michael Sun, Ph.D. - % - Takes 4D fmri_data() object or cell-array of fmri_data() - % - PREPROC_PARAMS: struct object that contains the fields: TR, R, hpf, and smooth - % - HRF_PARAMS: struct object that contains the fields: Condition, CondNames, TR, T, FWHM, alpha, and types. - % - at: atlas object - % - rois: cell-array of labels that match labels in at.labels - % - outfile: Desired filepath without extension. Extensions will be appended. - % - % *Usage: - % :: - % [tc, HRF] = EstimateHRF_inAtlas(image_obj, PREPROC_PARAMS, HRF_PARAMS, at, rois, outfile}) - % - % TODO: Pass in estHRF directory to reuse nifti files. - - % Step 0. Check if a directory exists that has estHRF .nii outputs - % already. - - % Step 0. Load in an SPM.mat file if available to pass in metadata and - % such - - - % Check if its an SPM struct or filepath: - if ischar(fmri_d) - if contains(fmri_d, 'SPM.mat') - load(fmri_d); - if exist('SPM', 'var') - isSPM=true; - end - - else - fmri_d=fmri_data(fmri_d); - end - end - - if isstruct(fmri_d) - SPM=fmri_d; - isSPM=true; - end - - if ~exist('isSPM') - isSPM = false; - end - - if isSPM - % if isstruct(varargin{1}) - % SPM=varargin{1}; - % else - % % If pointing to an SPM filepath - % load(varargin{1}) - % end - % % Don't do that, spmify instead into gKWY (grand-mean-scaled, filtered (K), and whitened (W)) - % gKWY=spmify(fmri_d, SPM); - % - % for d=1:numel(gKWY) - % preproc_dat{d}=fmri_d{d}; - % preproc_dat{d}.dat=gKWY{d}; - % end - [tc, HRF, HRF_OBJ, PARAM_OBJ]=roiTS_fitHRF_SPM(SPM, HRF_PARAMS, rois, at, outfile); - - else - % Step 1. Preprocess - if numel(fmri_d) == 1 - preproc_dat=canlab_connectivity_preproc(fmri_d, PREPROC_PARAMS.R, 'hpf', PREPROC_PARAMS.hpf, PREPROC_PARAMS.TR, 'average_over', 'no_plots'); - % Step 2. Smooth - preproc_dat=preprocess(preproc_dat, 'smooth', PREPROC_PARAMS.smooth); - else - - for d=1:numel(fmri_d) - [preproc_dat{d}]=canlab_connectivity_preproc(fmri_d, PREPROC_PARAMS.R{d}, 'hpf', PREPROC_PARAMS.hpf, PREPROC_PARAMS.TR, 'average_over', 'no_plots'); - % Step 2. Smooth - preproc_dat{d}=preprocess(preproc_dat{d}, 'smooth', PREPROC_PARAMS.smooth); - end - - end - - % Step 3. fitHRF to each ROI's worth of data - [tc, HRF]=roiTS_fitHRF(preproc_dat, HRF_PARAMS, rois, at, outfile); - end - - - if numel(HRF)>1 - for i=1:numel(HRF) - HRF{i}.preproc_params=PREPROC_PARAMS; - end - else - HRF.preproc_params=PREPROC_PARAMS; - end - - - % Step 4. Save your Results - try - % saveas(gcf, [outfile, '.png']) - save([outfile, '.mat'], 'tc', 'HRF', '-v7.3'); - catch - warning([outfile, ' files could not be saved.']); - end -end - - -%% HELPER FUNCTIONS -% function [HRF_OBJ, HRF]=gen_HRFimg(preproc_dat, HRF_PARAMS, rois, at, outfile) -% -% % This is finished but untested. Intention is to generate a directory from -% % HRF images in BIDS format. -% -% % Make directories for files if needed -% if ~isempty(fileparts(outfile)) -% if ~exist(fileparts(outfile), 'dir') -% mkdir(fileparts(outfile)); -% end -% end -% -% % Make sure CondNames are valid before continuing: -% HRF_PARAMS.CondNames=matlab.lang.makeValidName(HRF_PARAMS.CondNames); -% -% HRF.atlas=at; -% HRF.region=rois; -% HRF.types=HRF_PARAMS.types; -% HRF.name=preproc_dat.image_names; -% -% % Initialize the parallel pool if it's not already running -% if isempty(gcp('nocreate')) -% parpool; -% end -% -% % Write out the images for later post-analyses -% % [~, fname, ~]=fileparts(preproc_dat.image_names); -% % fname=outfile -% -% parfor t=1:numel(HRF_PARAMS.types) -% % for t=1:numel(HRF_PARAMS.types) % FOR TROUBLESHOOTING -% warning('off', 'all'); -% switch HRF_PARAMS.types{t} -% case 'IL' -% [~, ~, PARAM_OBJ, HRF_OBJ] = hrf_fit(preproc_dat, HRF_PARAMS.TR, HRF_PARAMS.Condition, HRF_PARAMS.T, HRF_PARAMS.types{t}, 0); -% -% case 'FIR' -% [~, ~, PARAM_OBJ, HRF_OBJ] = hrf_fit(preproc_dat, HRF_PARAMS.TR, HRF_PARAMS.Condition, HRF_PARAMS.T, HRF_PARAMS.types{t}, 1); -% -% case 'CHRF' -% [~, ~, PARAM_OBJ, HRF_OBJ] = hrf_fit(preproc_dat, HRF_PARAMS.TR, HRF_PARAMS.Condition, HRF_PARAMS.T, HRF_PARAMS.types{t}, 2); -% otherwise -% error('No valid fit-type. Choose IL, FIR, or CHRF') -% end -% -% for c=1:numel(HRF_PARAMS.Condition) -% -% HRF_OBJ{c}.fullpath=sprintf([outfile, '_type-', HRF_PARAMS.types{t}, '_condition-', HRF_PARAMS.CondNames{c}, '_fit.nii']); -% PARAM_OBJ{c}.fullpath=sprintf([outfile, '_type-', HRF_PARAMS.types{t}, '_condition-', HRF_PARAMS.CondNames{c}, '_params.nii']); -% try -% write(HRF_OBJ{c}, 'overwrite'); -% write(PARAM_OBJ{c}, 'overwrite'); -% catch -% warning('Not able to write one or more files.'); -% end -% -% end -% -% end -% end - - -% function [tc, HRF]=gen_HRFstruct_from_dir(preproc_dat, HRF_PARAMS, rois, at, outfile, estHRF_dir, HRF_OBJ) -% -% % This is unfinished. Intention is to take a generated directory from -% % gen_HRFimg and generate an HRF structure from it. -% -% % Initialize the parallel pool if it's not already running -% if isempty(gcp('nocreate')) -% parpool; -% end -% -% % Initialize 'tc' and 'temp_HRF_fit' cell arrays -% tc = cell(1, numel(HRF_PARAMS.types)); -% temp_HRF_fit = cell(1, numel(HRF_PARAMS.types)); -% -% parfor t=1:numel(HRF_PARAMS.types) -% -% HRF_local = cell(1, numel(rois)); -% tc_local = cell(1, numel(rois)); -% -% -% % Consider doing apply_parcellation instead of mean(apply_mask(HRF_OBJ{c}, at.select_atlas_subset(rois(r), 'exact')).dat); -% % [parcel_means, parcel_pattern_expression, parcel_valence, rmsv_pos, rmsv_neg] = apply_parcellation(dat,at); -% % nps=load_image_set('npsplus'); -% % nps = get_wh_image(nps,1); -% % [parcel_means, parcel_pattern_expression, parcel_valence, rmsv_pos, rmsv_neg] = apply_parcellation(dat,at, 'pattern_expression', nps); -% % r=region(at,'unique_mask_values'); -% % wh_parcels=~all(isnan(parcel_means)) -% -% for r=1:numel(rois) -% tic -% for c=1:numel(HRF_PARAMS.CondNames) -% -% try -% tc_local{r}{c}=mean(apply_mask(HRF_OBJ{c}, at.select_atlas_subset(rois(r), 'exact')).dat); -% -% HRF_local{r}.(HRF_PARAMS.CondNames{c}).model=tc_local{r}{c}; -% [HRF_local{r}.(HRF_PARAMS.CondNames{c}).peaks, HRF_local{r}.(HRF_PARAMS.CondNames{c}).troughs]=detectPeaksTroughs(tc_local{r}{c}', false); -% -% [~, regionVoxNum, ~, ~]=at.select_atlas_subset(rois(r), 'exact').get_region_volumes; -% HRF_local{r}.(HRF_PARAMS.CondNames{c}).model_voxnormed=tc_local{r}{c}/regionVoxNum; -% [HRF_local{r}.(HRF_PARAMS.CondNames{c}).peaks_voxnormed, HRF_local{r}.(HRF_PARAMS.CondNames{c}).troughs_voxnormed]=detectPeaksTroughs(tc_local{r}{c}'/regionVoxNum, false); -% catch -% disp(t); -% disp(c); -% disp(rois{r}); -% tc_local{r}{c} -% mean(apply_mask(HRF_OBJ{c}, at.select_atlas_subset(rois(r), 'exact')).dat) -% {apply_mask(HRF_OBJ{c}, at.select_atlas_subset(rois(r), 'exact')).dat} -% HRF_OBJ{c} -% -% end -% -% % Number of phases -% start_times = [HRF_local{r}.(HRF_PARAMS.CondNames{c}).peaks.start_time, HRF_local{r}.(HRF_PARAMS.CondNames{c}).troughs.start_time]; -% end_times = [HRF_local{r}.(HRF_PARAMS.CondNames{c}).peaks.end_time, HRF_local{r}.(HRF_PARAMS.CondNames{c}).troughs.end_time]; -% phases = [start_times(:), end_times(:)]; -% unique_phases = unique(phases, 'rows'); -% HRF_local{r}.(HRF_PARAMS.CondNames{c}).phases = mat2cell(unique_phases, ones(size(unique_phases, 1), 1), 2); -% -% % Loop over phases -% for p = 1:numel(HRF_local{r}.(HRF_PARAMS.CondNames{c}).phases) -% features = {'peaks', 'troughs'}; -% for f = 1:2 -% feat = features{f}; -% -% % Count the number of features (peaks or troughs) -% start_times = cell2mat({HRF_local{r}.(HRF_PARAMS.CondNames{c}).(feat).start_time}); -% current_phase_start = HRF_local{r}.(HRF_PARAMS.CondNames{c}).phases{p}(1); -% HRF_local{r}.(HRF_PARAMS.CondNames{c}).phase(p).(feat) = sum(start_times == current_phase_start); -% -% if HRF_local{r}.(HRF_PARAMS.CondNames{c}).phase(p).(feat) > 0 -% display(['Estimating ' , num2str(t), '_', rois{r}, '_', HRF_PARAMS.CondNames{c}, '_', ' Now...!']); -% display(['Phase ' , num2str(p), 'Feature ', feat]); -% idx = find(start_times == current_phase_start); -% auc = cell2mat({HRF_local{r}.(HRF_PARAMS.CondNames{c}).(feat).AUC}); -% height = cell2mat({HRF_local{r}.(HRF_PARAMS.CondNames{c}).(feat).height}); -% time_to_peak = cell2mat({HRF_local{r}.(HRF_PARAMS.CondNames{c}).(feat).time_to_peak}); -% half_height = cell2mat({HRF_local{r}.(HRF_PARAMS.CondNames{c}).(feat).half_height}); -% -% feat_voxnormed = strcat(feat, '_voxnormed'); -% auc_voxnormed = cell2mat({HRF_local{r}.(HRF_PARAMS.CondNames{c}).(feat_voxnormed).AUC}); -% height_voxnormed = cell2mat({HRF_local{r}.(HRF_PARAMS.CondNames{c}).(feat_voxnormed).height}); -% time_to_peak_voxnormed = cell2mat({HRF_local{r}.(HRF_PARAMS.CondNames{c}).(feat_voxnormed).time_to_peak}); -% half_height_voxnormed = cell2mat({HRF_local{r}.(HRF_PARAMS.CondNames{c}).(feat_voxnormed).half_height}); -% -% HRF_local{r}.(HRF_PARAMS.CondNames{c}).phase(p).auc = unique(auc(idx)); -% HRF_local{r}.(HRF_PARAMS.CondNames{c}).phase(p).auc_voxnormed = unique(auc_voxnormed(idx)); -% -% HRF_local{r}.(HRF_PARAMS.CondNames{c}).phase(p).height = height(idx); -% HRF_local{r}.(HRF_PARAMS.CondNames{c}).phase(p).height_voxnormed = height_voxnormed(idx); -% HRF_local{r}.(HRF_PARAMS.CondNames{c}).phase(p).time_to_peak = time_to_peak(idx); -% HRF_local{r}.(HRF_PARAMS.CondNames{c}).phase(p).time_to_peak_voxnormed = time_to_peak_voxnormed(idx); -% HRF_local{r}.(HRF_PARAMS.CondNames{c}).phase(p).half_height = half_height(idx); -% HRF_local{r}.(HRF_PARAMS.CondNames{c}).phase(p).half_height_voxnormed = half_height_voxnormed(idx); -% end -% end -% end -% end -% display(strjoin({num2str(t), ' Done in ', num2str(toc), ' seconds with ', rois{r}})); -% -% end -% % Save the results for this ROI -% display([num2str(t), ' Done!']) -% temp_HRF_fit{t} = HRF_local; -% tc{t} = tc_local; -% end -% -% % Transfer the results from the temporary cell array to the HRF structure -% HRF.fit = temp_HRF_fit; -% HRF.params=HRF_PARAMS; -% -% delete(gcp('nocreate')); -% -% -% end - - -function [tc, HRF] = roiTS_fitHRF(preproc_dat, HRF_PARAMS, rois, at, outfile, varargin) - - if ~isempty(fileparts(outfile)) - disp(fileparts(outfile)); - if ~exist(fileparts(outfile), 'dir') - mkdir(fileparts(outfile)); - end - end - - HRF_PARAMS.CondNames = matlab.lang.makeValidName(HRF_PARAMS.CondNames); - - HRF.atlas = at; - HRF.region = rois; - HRF.types = HRF_PARAMS.types; - - % Force data into cell form for consistent indexing - if ~iscell(preproc_dat) - preproc_dat = {preproc_dat}; - end - - nData = numel(preproc_dat); - nTypes = numel(HRF_PARAMS.types); - nCond = numel(HRF_PARAMS.CondNames); - - HRF.name = cell(1, nData); - tc = cell(nData, nTypes); - HRF.fit = cell(nData, nTypes); - HRF_OBJ = cell(nData, nTypes); - PARAM_OBJ = cell(nData, nTypes); - - for d = 1:nData - HRF.name{d} = preproc_dat{d}.image_names; - end - - if numel(HRF_PARAMS.T) == 1 - HRF_PARAMS.T = repmat(HRF_PARAMS.T, 1, nCond); - end - - - if isempty(gcp('nocreate')) - parpool; - end - - parfor d = 1:nData - % for d = 1:nData - - data = preproc_dat{d}; - - local_HRF_OBJ = cell(1, nTypes); - local_PARAM_OBJ = cell(1, nTypes); - - for t = 1:nTypes - - warning('off', 'all'); - - switch HRF_PARAMS.types{t} - case 'IL' - [~, ~, local_PARAM_OBJ{t}, local_HRF_OBJ{t}] = ... - hrf_fit(data, HRF_PARAMS.TR, HRF_PARAMS.Condition, HRF_PARAMS.T, 'IL', 0); - - case 'FIR' - [~, ~, local_PARAM_OBJ{t}, local_HRF_OBJ{t}] = ... - hrf_fit(data, HRF_PARAMS.TR, HRF_PARAMS.Condition, HRF_PARAMS.T, 'FIR', 0); - - case 'sFIR' - [~, ~, local_PARAM_OBJ{t}, local_HRF_OBJ{t}] = ... - hrf_fit(data, HRF_PARAMS.TR, HRF_PARAMS.Condition, HRF_PARAMS.T, 'FIR', 1); - - case 'CHRF0' - [~, ~, local_PARAM_OBJ{t}, local_HRF_OBJ{t}] = ... - hrf_fit(data, HRF_PARAMS.TR, HRF_PARAMS.Condition, HRF_PARAMS.T, 'CHRF', 0); - - case 'CHRF1' - [~, ~, local_PARAM_OBJ{t}, local_HRF_OBJ{t}] = ... - hrf_fit(data, HRF_PARAMS.TR, HRF_PARAMS.Condition, HRF_PARAMS.T, 'CHRF', 1); - - case 'CHRF2' - [~, ~, local_PARAM_OBJ{t}, local_HRF_OBJ{t}] = ... - hrf_fit(data, HRF_PARAMS.TR, HRF_PARAMS.Condition, HRF_PARAMS.T, 'CHRF', 2); - - otherwise - error('No valid fit-type. Choose IL, FIR/sFIR or CHRF0/CHRF1/CHRF2'); - end - - % write each condition-specific image for this type - for c = 1:nCond - local_HRF_OBJ{t}{c}.fullpath = [outfile ... - '_type-' HRF_PARAMS.types{t} ... - '_condition-' HRF_PARAMS.CondNames{c} '_fit.nii']; - - local_PARAM_OBJ{t}{c}.fullpath = [outfile ... - '_type-' HRF_PARAMS.types{t} ... - '_condition-' HRF_PARAMS.CondNames{c} '_params.nii']; - - try - write(local_HRF_OBJ{t}{c}, 'overwrite'); - write(local_PARAM_OBJ{t}{c}, 'overwrite'); - catch - warning('Not able to write one or more files.'); - end - end - end - - HRF_OBJ(d, :) = local_HRF_OBJ; - PARAM_OBJ(d, :) = local_PARAM_OBJ; - end - - % Extract ROI HRFs: one entry per data x type, with ALL conditions - for d = 1:nData - for t = 1:nTypes - [HRF.fit{d,t}, tc{d,t}] = extractHRF( ... - HRF_OBJ{d,t}, ... - HRF_PARAMS.CondNames, ... - 'atlas', at, ... - 'regions', rois); - end - end - - HRF.CondNames = HRF_PARAMS.CondNames; - - delete(gcp('nocreate')); -end - - - - -% function [tc, HRF]=roiTS_fitHRF(preproc_dat, HRF_PARAMS, rois, at, outfile, varargin) -% -% % Make directories for files if needed -% if ~isempty(fileparts(outfile)) -% disp(fileparts(outfile)); -% if ~exist(fileparts(outfile), 'dir') -% mkdir(fileparts(outfile)); -% end -% end -% -% % Make sure CondNames are valid before continuing: -% HRF.atlas=at; -% HRF.region=rois; -% HRF.types=HRF_PARAMS.types; -% -% if iscell(preproc_dat) -% for c=1:numel(preproc_dat) -% HRF.name{c}=preproc_dat{c}.image_names; -% -% % Initialize 'tc' and 'temp_HRF_fit' cell arrays -% tc{c} = cell(1, numel(HRF_PARAMS.types)); -% temp_HRF_fit{c} = cell(1, numel(HRF_PARAMS.types)); -% end -% else -% HRF.name=preproc_dat.image_names; -% -% % Initialize 'tc' and 'temp_HRF_fit' cell arrays -% tc = cell(1, numel(HRF_PARAMS.types)); -% temp_HRF_fit = cell(1, numel(HRF_PARAMS.types)); -% end -% -% % Carve up SPM's design matrix for each image -% % if ~isempty(varargin) -% % if ischar(varargin{1}) || isstring(varargin{1}) -% % load(varargin{1}); -% % elseif isstruct(varargin{1}) -% % SPM=varargin{1}; -% % end -% -% -% % DX=cell(1,numel(SPM.nscan)); -% % if numel(preproc_dat) == numel(SPM.nscan) -% % for d=1:numel(preproc_dat) -% % % Check if the SPM file accounts for the same number of scans -% % -% % % HRF_PARAMS.CondNames -% % % Use regexp to search for the pattern -% % % hot_matches = ~cellfun(@isempty, regexp(SPM.xX.name, ['Sn\(', num2str(i), '\).*_heat_start'])); -% % % warm_matches = ~cellfun(@isempty, regexp(SPM.xX.name, ['Sn\(', num2str(i), '\).*_warm_start'])); -% % % imgcue_matches = ~cellfun(@isempty, regexp(SPM.xX.name, ['Sn\(', num2str(i), '\).*_imagine_cue'])); -% % % imag_matches = ~cellfun(@isempty, regexp(SPM.xX.name, ['Sn\(', num2str(i), '\).*_imagine_start'])); -% % -% % R_matches = ~cellfun(@isempty, regexp(SPM.xX.name, ['Sn\(', num2str(d), '\).*R.*'])); -% % constant_matches = ~cellfun(@isempty, regexp(SPM.xX.name, ['Sn\(', num2str(d), '\).*constant'])); -% % sess_cols = ~cellfun(@isempty, regexp(SPM.xX.name, ['Sn\(', num2str(d), '\).*'])); -% % -% % -% % % Find indices of matches -% % indices = [find(sess_cols)]; -% % % indices = [find(hot_matches) find(warm_matches) find(imgcue_matches) find(imag_matches)]; -% % % indices = [find(R_matches) find(constant_matches)]; -% % task_regressors{d} = [find(sess_cols & (~R_matches & ~constant_matches))]; -% % -% % % Each design matrix needs task regressors, covariates, and -% % % intercept -% % DX{d}=SPM.xX.xKXs.X(SPM.Sess(d).row, indices); -% % -% % end -% % customDX=1; -% % end -% % -% % -% % end -% -% HRF_PARAMS.CondNames=matlab.lang.makeValidName(HRF_PARAMS.CondNames); -% -% -% -% % Initialize the parallel pool if it's not already running -% if isempty(gcp('nocreate')) -% parpool; -% end -% -% -% % Write out the images for later post-analyses -% % [~, fname, ~]=fileparts(preproc_dat.image_names); -% % fname=outfile -% -% HRF_OBJ=cell(1,numel(preproc_dat)); -% PARAM_OBJ=cell(1,numel(preproc_dat)); -% -% parfor d=1:numel(preproc_dat) -% % for d=1:numel(preproc_dat) -% -% if iscell(preproc_dat) -% data=preproc_dat{d}; -% else -% data = preproc_dat; -% end -% -% for t=1:numel(HRF_PARAMS.types) -% -% warning('off', 'all'); -% switch HRF_PARAMS.types{t} -% case 'IL' -% % [~, ~, PARAM_OBJ{d}, HRF_OBJ{d}] = hrf_fit(data, HRF_PARAMS.TR, HRF_PARAMS.Condition(d), HRF_PARAMS.T, HRF_PARAMS.types{t}, 0); -% [~, ~, PARAM_OBJ{d,t}, HRF_OBJ{d,t}] = hrf_fit(data, HRF_PARAMS.TR, HRF_PARAMS.Condition(d), HRF_PARAMS.T, HRF_PARAMS.types{t}, 0); -% case 'FIR' -% % [~, ~, PARAM_OBJ{d}, HRF_OBJ{d}] = hrf_fit(data, HRF_PARAMS.TR, HRF_PARAMS.Condition(d), HRF_PARAMS.T, HRF_PARAMS.types{t}, 0); -% [~, ~, PARAM_OBJ{d,t}, HRF_OBJ{d,t}] = hrf_fit(data, HRF_PARAMS.TR, HRF_PARAMS.Condition(d), HRF_PARAMS.T, HRF_PARAMS.types{t}, 0); -% case 'sFIR' -% % [~, ~, PARAM_OBJ{d}, HRF_OBJ{d}] = hrf_fit(data, HRF_PARAMS.TR, HRF_PARAMS.Condition(d), HRF_PARAMS.T, 'FIR', 1); -% [~, ~, PARAM_OBJ{d,t}, HRF_OBJ{d,t}] = hrf_fit(data, HRF_PARAMS.TR, HRF_PARAMS.Condition(d), HRF_PARAMS.T, 'FIR', 1); -% case 'CHRF0' -% % [~, ~, PARAM_OBJ{d}, HRF_OBJ{d}] = hrf_fit(data, HRF_PARAMS.TR, HRF_PARAMS.Condition(d), HRF_PARAMS.T, 'CHRF', 0); -% [~, ~, PARAM_OBJ{d,t}, HRF_OBJ{d,t}] = hrf_fit(data, HRF_PARAMS.TR, HRF_PARAMS.Condition(d), HRF_PARAMS.T, 'CHRF', 0); -% case 'CHRF1' -% % [~, ~, PARAM_OBJ{d}, HRF_OBJ{d}] = hrf_fit(data, HRF_PARAMS.TR, HRF_PARAMS.Condition(d), HRF_PARAMS.T, 'CHRF', 1); -% [~, ~, PARAM_OBJ{d,t}, HRF_OBJ{d,t}] = hrf_fit(data, HRF_PARAMS.TR, HRF_PARAMS.Condition(d), HRF_PARAMS.T, 'CHRF', 1); -% case 'CHRF2' -% % [~, ~, PARAM_OBJ{d}, HRF_OBJ{d}] = hrf_fit(data, HRF_PARAMS.TR, HRF_PARAMS.Condition(d), HRF_PARAMS.T, 'CHRF', 2); -% [~, ~, PARAM_OBJ{d,t}, HRF_OBJ{d,t}] = hrf_fit(data, HRF_PARAMS.TR, HRF_PARAMS.Condition(d), HRF_PARAMS.T, 'CHRF', 2); -% -% otherwise -% error('No valid fit-type. Choose IL, FIR/sFIR or CHRF0/CHRF1/CHRF2') -% end -% -% for c=1:numel(HRF_PARAMS.Condition) -% -% HRF_OBJ{d}{c}.fullpath=[outfile, '_type-', HRF_PARAMS.types{t}, '_condition-', HRF_PARAMS.CondNames{c}, '_fit.nii']; -% PARAM_OBJ{d}{c}.fullpath=[outfile, '_type-', HRF_PARAMS.types{t}, '_condition-', HRF_PARAMS.CondNames{c}, '_params.nii']; -% try -% write(HRF_OBJ{d}{c}, 'overwrite'); -% write(PARAM_OBJ{d}{c}, 'overwrite'); -% catch -% warning('Not able to write one or more files.'); -% end -% -% end -% -% % HRF_local{d} = cell(1, numel(rois)); -% % tc_local{d} = cell(1, numel(rois)); -% -% end -% end -% -% % Generate an HRF and tc for every datafile and concatenate them -% % together. -% -% for d=1:numel(HRF_OBJ) -% for t=1:numel(HRF_OBJ{d}) -% if exist('SPM') -% [HRF.fit{d,t}, tc{d,t}]=extractHRF(HRF_OBJ{d}, [SPM.Sess(i).U.name], 'atlas', at, 'regions', rois); -% else -% [HRF.fit{d,t}, tc{d,t}]=extractHRF(HRF_OBJ{d}, HRF_PARAMS.CondNames(d), 'atlas', at, 'regions', rois); -% end -% end -% end -% -% % This results in an HRF struct: HRF(d, t, r, c), tc(d,t,r,c) -% -% HRF.CondNames = HRF_PARAMS.CondNames; -% -% delete(gcp('nocreate')); -% end - -function [tc, HRF, HRF_OBJ, PARAM_OBJ]=roiTS_fitHRF_SPM(SPM, HRF_PARAMS, rois, at, outfile) - - % Make directories for files if needed - if ~isempty(fileparts(outfile)) - disp(fileparts(outfile)); - if ~exist(fileparts(outfile), 'dir') - mkdir(fileparts(outfile)); - end - end - - % Make sure CondNames are valid before continuing: - HRF.atlas=at; - HRF.region=rois; - HRF.types=HRF_PARAMS.types; - HRF.name=unique({SPM.xY.VY.fname}'); - - if numel(HRF.name)>1 - for c=1:numel(HRF.name) - % Initialize 'tc' and 'temp_HRF_fit' cell arrays - tc{c} = cell(1, numel(HRF_PARAMS.types)); - temp_HRF_fit{c} = cell(1, numel(HRF_PARAMS.types)); - end - else - % Initialize 'tc' and 'temp_HRF_fit' cell arrays - tc = cell(1, numel(HRF_PARAMS.types)); - temp_HRF_fit = cell(1, numel(HRF_PARAMS.types)); - end - - % Carve up SPM's design matrix for each image - % DX=cell(1,numel(SPM.nscan)); - - % HRF_PARAMS.CondNames=matlab.lang.makeValidName(HRF_PARAMS.CondNames); - - HRF_OBJ=cell(1,numel(HRF_PARAMS.types)); - PARAM_OBJ=cell(1,numel(HRF_PARAMS.types)); - info=cell(1,numel(HRF_PARAMS.types)); - - % Initialize the parallel pool if it's not already running - if isempty(gcp('nocreate')) - parpool; - end - - CondNames=cell(1, numel(HRF_PARAMS.types)); - - parfor t=1:numel(HRF_PARAMS.types) - % for t=1:numel(HRF_PARAMS.types) - - - % First check to see if valid images have already been generated. Then - % we don't have to regenerate them. - files_exist=0; - % for d = 1:numel(HRF.name) - % - % CondNames{t}{d} = strtrim([SPM.Sess(d).U.name]'); - % for c=1:numel(CondNames{t}{d}) - % HRF_OBJ_path=[outfile, '_type-', HRF_PARAMS.types{t}, '_condition-', CondNames{t}{d}{c}, '_fit.nii']; - % PARAM_OBJ_path=[outfile, '_type-', HRF_PARAMS.types{t}, '_condition-', CondNames{t}{d}{c}, '_params.nii']; - % if exist(HRF_OBJ_path, 'file') - % HRF_OBJ{t}{d}{c}=fmri_data(HRF_OBJ_path); - % PARAM_OBJ{t}{d}{c}=fmri_data(PARAM_OBJ_path); - % files_exist=1; - % % disp('found file') - % else - % % disp('did not find file') - % files_exist=0; - % end - % end - % end - - if ~files_exist - % Generate the files otherwise. - warning('off', 'all'); - if contains(HRF_PARAMS.types{t}, 'IL'), [~, ~, PARAM_OBJ{t}, HRF_OBJ{t}] = hrf_fit(SPM, HRF_PARAMS.T, 'IL', 0);, end - if contains(HRF_PARAMS.types{t}, 'FIR'), [~, ~, PARAM_OBJ{t}, HRF_OBJ{t}, info{t}] = hrf_fit(SPM, HRF_PARAMS.T, 'FIR', 0);, end - if contains(HRF_PARAMS.types{t}, 'sFIR'), [~, ~, PARAM_OBJ{t}, HRF_OBJ{t}, info{t}] = hrf_fit(SPM, HRF_PARAMS.T, 'FIR', 1);, end - if contains(HRF_PARAMS.types{t}, 'CHRF0'), [~, ~, PARAM_OBJ{t}, HRF_OBJ{t}, info{t}] = hrf_fit(SPM, HRF_PARAMS.T, 'CHRF', 0);, end - if contains(HRF_PARAMS.types{t},'CHRF1'), [~, ~, PARAM_OBJ{t}, HRF_OBJ{t}, info{t}] = hrf_fit(SPM, HRF_PARAMS.T, 'CHRF', 1);, end - if contains(HRF_PARAMS.types{t}, 'CHRF2'), [~, ~, PARAM_OBJ{t}, HRF_OBJ{t}, info{t}] = hrf_fit(SPM, HRF_PARAMS.T, 'CHRF', 2);, end - - if ~ismember(HRF_PARAMS.types{t}, {'IL', 'FIR', 'sFIR','CHRF0','CHRF1','CHRF2'}) - error('Not a valid fit-type. Choose IL, FIR/sFIR or CHRF0/CHRF1/CHRF2') - end - - % The issue with this one is that now HRF_OBJ{t} and PARAM_OBJ{t} - % are cell arrays with cells for each data file d. - - for d = 1:numel(info{t}) - - CondNames{t}{d} = strtrim(info{t}{d}.names); - - for c=1:numel(CondNames{t}{d}) - - HRF_OBJ{t}{d}{c}.fullpath=[outfile, '_type-', HRF_PARAMS.types{t}, '_condition-', CondNames{t}{d}{c}, '_fit.nii']; - PARAM_OBJ{t}{d}{c}.fullpath=[outfile, '_type-', HRF_PARAMS.types{t}, '_condition-', CondNames{t}{d}{c}, '_params.nii']; - try - write(HRF_OBJ{t}{d}{c}, 'overwrite'); - write(PARAM_OBJ{t}{d}{c}, 'overwrite'); - catch - warning('Not able to write one or more files.'); - end - - end - - end - end - end - - % This will return an HRF_OBJ that is sectioned by: - % HRF Type (t), Run(d), Region (r), Condition (c) - - % Rearrange HRF_OBJ{t, d, c} and PARAM_OBJ{t, d, c} - HRF.CondNames=CondNames{1}; - - - % Generate an HRF and tc for every HRF_OBJ datafile (type x session x Condition) and concatenate them - % together. - - HRF_arr=cell(1,numel(HRF_OBJ)); - for d=1:numel(HRF.name) % Organize by Session, then type, then condition: - HRF_struct=HRF; - HRF_struct.name=HRF.name{d}; - HRF_struct.CondNames=HRF.CondNames{d}; - tic - for t=1:numel(HRF.types) - if exist('SPM') - [HRF_struct.fit{t}, tc{d}{t}]=extractHRF(HRF_OBJ{t}{d}, [SPM.Sess(d).U.name], at, rois); - else - [HRF_struct.fit{t}, tc{d}{t}]=extractHRF(HRF_OBJ{t}{d}, HRF.CondNames{d}, at, rois); - end - end - toc - - HRF_arr{d}=HRF_struct; - end - - HRF=HRF_arr; - - - - % This results in an HRF struct: HRF(d, t, r, c), tc(d,t,r,c) - - - -end \ No newline at end of file diff --git a/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/HRF_avg.m b/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/HRF_avg.m deleted file mode 100644 index 7a3e80ec..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/HRF_avg.m +++ /dev/null @@ -1,400 +0,0 @@ -% I'll have to put these all together and graph them -% for HRF.region - -% for HRF.types - -function avgHRF = HRF_avg(HRF_cell_array, varargin) - % Check if all HRFs have the same atlas - - % Detect if the HRF_cell array is sessions or runs? - - - - % Suppress all warnings for now, since they'll mostly flood the screen - % with Invalid Heights from findpeaks() - warning('off', 'all'); - - hasAtlas=0; - hasRegions=0; - hasConditions=0; - hasFit=0; - - for k = 1:length(varargin) - if strcmpi(varargin{k}, 'atlas') - if isa(varargin{k+1}, 'atlas') - avgHRF.atlas=varargin{k+1}; - hasAtlas = true; - else - error('Passed in atlas not an atlas.'); - end - end - - if strcmpi(varargin{k}, 'regions') - if ischar(varargin{k+1}) - avgHRF.region=varargin{k+1}; - % if ~ismember(avgHRF.region, HRF.region) - % error('Invalid region specified.'); - % else - hasRegions = true; - % end - elseif iscell(varargin{k+1}) - avgHRF.region=varargin{k+1}; - hasRegions = true; - end - end - - if strcmpi(varargin{k}, 'conditions') - if ischar(varargin{k+1}) - avgHRF.CondNames=varargin{k+1}; - % if ~ismember(avgHRF.CondNames, HRF.CondNames) - % error('Invalid condition specified.'); - % else - hasConditions = true; - % end - elseif iscell(varargin{k+1}) - avgHRF.CondNames=varargin{k+1}; - hasConditions = true; - % else - % disp(['Input argument for condition unknown.']); - % end - end - end - - if strcmpi(varargin{k}, 'fit') - if ischar(varargin{k+1}) - avgHRF.types=varargin{k+1}; - % if ~ismember(avgHRF.types, HRF.types) - error('Invalid fit-type specified.'); - % else - hasFit = true; - % end - elseif iscell(varargin{k+1}) - avgHRF.types=varargin{k+1}; - hasFit = true; - else - disp(['Input argument for fit-type unknown.']); - end - end - - end - - if ~hasAtlas - try - first_atlas = HRF_cell_array{1}.atlas; - catch - first_atlas=load_atlas('canlab2023'); - end - all_same_atlas = true; - - for i = 2:numel(HRF_cell_array) - if ~isequal(HRF_cell_array{i}.atlas, first_atlas) - all_same_atlas = false; - end - end - - if all_same_atlas - avgHRF.atlas=first_atlas; - else - error('HRF Structures have different .atlas values.\n'); - end - end - - if ~hasFit - first_types = HRF_cell_array{1}.types; - all_same_types = true; - - for i = 2:numel(HRF_cell_array) - if ~isequal(HRF_cell_array{i}.types, first_types) - all_same_types = false; - end - end - if all_same_types - avgHRF.types=first_types; - else - error('HRF Structures have different .types values.\n'); - end - end - - if ~hasConditions - first_conds = HRF_cell_array{1}.CondNames; - all_same_conds = true; - - for i = 2:numel(HRF_cell_array) - if ~isequal(HRF_cell_array{i}.CondNames, first_conds) - all_same_conds = false; - end - end - if all_same_conds - avgHRF.CondNames=first_conds; - else - avgerror('HRF Structures have different .CondNames values.\n'); - end - end - - if ~hasRegions - first_region = HRF_cell_array{1}.region; - all_same_regions = true; - - for i = 2:numel(HRF_cell_array) - if ~isequal(HRF_cell_array{i}.region, first_region) - all_same_regions = false; - end - end - - if all_same_regions - avgHRF.region=first_region; - else - error('HRF Structures have different .region values.\n'); - end - end - - - % Extract the number of fits, regions, and conditions - numFits = numel(avgHRF.types); - numRegions = numel(avgHRF.region); - numConditions = numel(avgHRF.CondNames); - - % Loop through fits, regions, and conditions - for fitIndex = 1:numFits - - for regionIndex = 1:numRegions - - avgVector_across_conditions=[]; - for conditionIndex = 1:numConditions - models3d=[]; - - % condName = avgHRF.CondNames{conditionIndex}; - - % Initialize an empty array to store 'model' vectors - models = []; - phases=[]; - peaks=[]; - troughs=[]; - numPhases = 0; - numPeaks = 0; % Initialize the count of peak elements - numTroughs = 0; % Initialize the count of peak elements - - % Iterate through the structures and extract 'model' vectors - for dataIndex = 1:numel(HRF_cell_array) - fields=fieldnames(HRF_cell_array{dataIndex}.fit{fitIndex}{regionIndex}); - conditionName=char(fields(contains(fields, avgHRF.CondNames{conditionIndex}))); - - modelVector = HRF_cell_array{dataIndex}.fit{fitIndex}{regionIndex}.(conditionName).model; - [a, b]=padwithnan(models, modelVector, 2); - models = [a; b]; % Concatenate the vectors - - phaseStructs = HRF_cell_array{dataIndex}.fit{fitIndex}{regionIndex}.(conditionName).phases; - numPhases = numel(phaseStructs); - phases = [phases; numPhases]; - - peakStructs = HRF_cell_array{dataIndex}.fit{fitIndex}{regionIndex}.(conditionName).peaks; - numPeaks = numel(peakStructs); % Count the peak elements - peaks = [peaks; numPeaks]; - - troughStructs = HRF_cell_array{dataIndex}.fit{fitIndex}{regionIndex}.(conditionName).troughs; - numTroughs = numel(troughStructs); % Count the trough elements - troughs = [troughs; numTroughs]; - end - - % Compute the average vector for the current condition, region, and fit - avgVector = mean(models, 1); - [a, b]=padwithnan(avgVector_across_conditions, avgVector, 2); - avgVector_across_conditions=[a; b]; - - % Compute the standard deviation for the current condition, region, and fit - stdVector = std(models, 0, 1); % second parameter = 0 normalizes by N-1, which is typically desired when estimating the population standard deviation from a sample - wseVector=barplot_get_within_ste(models); - - avg_per_subject = mean(models, 2); - - avgPhases = mean(phases, 1); - stdPhases = std(phases, 0, 1); - - avgPeaks = mean(peaks, 1); - stdPeaks = std(peaks, 0, 1); - - avgTroughs = mean(troughs, 1); - stdTroughs = std(troughs, 0, 1); - - models3d=cat(3, models3d, models); - - conditionName=avgHRF.CondNames{conditionIndex}; - % Store the average vector in the avgStruct - avgHRF.fit{fitIndex}{regionIndex}.(conditionName).model = avgVector; - avgHRF.fit{fitIndex}{regionIndex}.(conditionName).stddev = stdVector; - avgHRF.fit{fitIndex}{regionIndex}.(conditionName).wse = wseVector; - - avgHRF.fit{fitIndex}{regionIndex}.(conditionName).avgpeaks = avgPeaks; - avgHRF.fit{fitIndex}{regionIndex}.(conditionName).stdpeaks = stdPeaks; - - avgHRF.fit{fitIndex}{regionIndex}.(conditionName).avgtroughs = avgTroughs; - avgHRF.fit{fitIndex}{regionIndex}.(conditionName).stdtroughs = stdTroughs; - - % Store the constituent vectors in the avgStruct - avgHRF.fit{fitIndex}{regionIndex}.(conditionName).models = models; - - [avgHRF.fit{fitIndex}{regionIndex}.(conditionName).peaks, avgHRF.fit{fitIndex}{regionIndex}.(conditionName).troughs]=detectPeaksTroughs(avgVector', false); - [~, region_numVox, ~, ~]=avgHRF.atlas.select_atlas_subset(avgHRF.region(regionIndex), 'exact').get_region_volumes; - [avgHRF.fit{fitIndex}{regionIndex}.(conditionName).peaks_voxnormed, avgHRF.fit{fitIndex}{regionIndex}.(conditionName).troughs_voxnormed]=detectPeaksTroughs(avgVector'/region_numVox, false); - - % Number of phases - start_times = [avgHRF.fit{fitIndex}{regionIndex}.(conditionName).peaks.start_time, avgHRF.fit{fitIndex}{regionIndex}.(conditionName).troughs.start_time]; - end_times = [avgHRF.fit{fitIndex}{regionIndex}.(conditionName).peaks.end_time, avgHRF.fit{fitIndex}{regionIndex}.(conditionName).troughs.end_time]; - phases = [start_times(:), end_times(:)]; - unique_phases = unique(phases, 'rows'); - avgHRF.fit{fitIndex}{regionIndex}.(conditionName).phases = mat2cell(unique_phases, ones(size(unique_phases, 1), 1), 2); - - % Loop over phases - for p = 1:numel(avgHRF.fit{fitIndex}{regionIndex}.(conditionName).phases) - features = {'peaks', 'troughs'}; - for f = 1:2 - feat = features{f}; - - % Count the number of features (peaks or troughs) - start_times = cell2mat({avgHRF.fit{fitIndex}{regionIndex}.(conditionName).(feat).start_time}); - current_phase_start = avgHRF.fit{fitIndex}{regionIndex}.(conditionName).phases{p}(1); - avgHRF.fit{fitIndex}{regionIndex}.(conditionName).phase(p).(feat) = sum(start_times == current_phase_start); - - if avgHRF.fit{fitIndex}{regionIndex}.(conditionName).phase(p).(feat) > 0 - % display(['Estimating ' , num2str(fitIndex), '_', rois{r}, '_', conditionName, '_', ' Now...!']) - % display(['Phase ' , num2str(p), 'Feature ', feat]) - idx = find(start_times == current_phase_start); - auc = cell2mat({avgHRF.fit{fitIndex}{regionIndex}.(conditionName).(feat).AUC}); - height = cell2mat({avgHRF.fit{fitIndex}{regionIndex}.(conditionName).(feat).height}); - time_to_peak = cell2mat({avgHRF.fit{fitIndex}{regionIndex}.(conditionName).(feat).time_to_peak}); - half_height = cell2mat({avgHRF.fit{fitIndex}{regionIndex}.(conditionName).(feat).half_height}); - - feat_voxnormed = strcat(feat, '_voxnormed'); - auc_voxnormed = cell2mat({avgHRF.fit{fitIndex}{regionIndex}.(conditionName).(feat_voxnormed).AUC}); - height_voxnormed = cell2mat({avgHRF.fit{fitIndex}{regionIndex}.(conditionName).(feat_voxnormed).height}); - time_to_peak_voxnormed = cell2mat({avgHRF.fit{fitIndex}{regionIndex}.(conditionName).(feat_voxnormed).time_to_peak}); - half_height_voxnormed = cell2mat({avgHRF.fit{fitIndex}{regionIndex}.(conditionName).(feat_voxnormed).half_height}); - - avgHRF.fit{fitIndex}{regionIndex}.(conditionName).phase(p).auc = unique(auc(idx)); - avgHRF.fit{fitIndex}{regionIndex}.(conditionName).phase(p).auc_voxnormed = unique(auc_voxnormed(idx)); - - avgHRF.fit{fitIndex}{regionIndex}.(conditionName).phase(p).height = height(idx); - avgHRF.fit{fitIndex}{regionIndex}.(conditionName).phase(p).height_voxnormed = height_voxnormed(idx); - avgHRF.fit{fitIndex}{regionIndex}.(conditionName).phase(p).time_to_peak = time_to_peak(idx); - avgHRF.fit{fitIndex}{regionIndex}.(conditionName).phase(p).time_to_peak_voxnormed = time_to_peak_voxnormed(idx); - avgHRF.fit{fitIndex}{regionIndex}.(conditionName).phase(p).half_height = half_height(idx); - avgHRF.fit{fitIndex}{regionIndex}.(conditionName).phase(p).half_height_voxnormed = half_height_voxnormed(idx); - end - end - end - end - end - % Calculate the within-subject SE for each time point NOT DONE - within_subject_SE= squeeze(std(avgVector_across_conditions, 0, 1)) / sqrt(size(models, 1)); - - end -end - -% -% HRF{file}.fit{1}{1}.hot.model -% HRF{file}.fit{1}{1}.warm.model -% HRF{file}.fit{1}{1}.imagine.model -% -% -% -% HRF{file}.fit{1}{1}.model.h - - - -% Plotting Demonstration: -% -% % Your time vector, average vector, and standard deviation vector -% time = 1:width(avgVector); % Replace with your actual time vector -% c=3 -% -% % Create a new figure -% figure; -% -% % Plot the average vector -% plot(time, avgVector_across_conditions(c,:), 'b', 'LineWidth', 2); -% hold on; -% -% % Calculate the upper and lower bounds of the standard deviation -% % upperBound = avgVector + stdVector; -% % lowerBound = avgVector - stdVector; -% -% % SE -% % upperBound = avgVector_across_conditions(c,:) + stdVector/sqrt(10); -% % lowerBound = avgVector_across_conditions(c,:) - stdVector/sqrt(10); -% -% -% % Create a shaded area for the standard deviation -% x = [time, fliplr(time)]; -% y = [upperBound, fliplr(lowerBound)]; -% fill(x, y, 'b', 'FaceAlpha', 0.2, 'EdgeAlpha', 0); -% -% % Labels and title for clarity -% xlabel('Time'); -% ylabel('Measurement'); -% title('Average Measurement with Standard Deviation'); -% legend('Average', '1 SD', 'Location', 'Best'); -% -% % Display the plot -% hold off; -% grid on; -% -% -% -% -% -% % Computing the within-subject SE -% % Assuming `data` is a 3D matrix: subjects x time points x conditions. -% -% num_subjects=10 -% num_timepoints=64 -% num_conditions=3 -% -% -% data=models3d; -% % Assuming `data` is a 3D matrix: subjects x time points x conditions. -% -% [num_subjects, num_timepoints, num_conditions] = size(data); -% -% % Calculating the mean across conditions for each subject and timepoint -% subject_means = mean(data, 3); -% -% % Calculating the standard deviation of the subject means across timepoints -% subject_std = std(subject_means, 0, 2); -% -% % Calculating the average standard deviation across subjects -% avg_std = mean(subject_std); -% -% % Calculating the within-subject standard error -% WSE = avg_std / sqrt(num_conditions); -% -% % Preallocate arrays for efficiency. -% mean_response = zeros(num_conditions, num_timepoints); -% -% % Loop through each condition, time point and calculate the mean response. -% for c = 1:num_conditions -% for t = 1:num_timepoints -% mean_response(c, t) = mean(data(:, t, c)); -% end -% -% % Optionally, plot the mean response with shaded WSE for each condition. -% figure; -% plot(mean_response(c, :), 'LineWidth', 2); % Plot the mean response. -% hold on; -% -% % Add shaded error (WSE). -% fill([1:num_timepoints, fliplr(1:num_timepoints)], ... -% [mean_response(c, :) + WSE, fliplr(mean_response(c, :) - WSE)], ... -% [0.9, 0.9, 0.9], 'EdgeColor', 'none'); -% -% hold off; -% xlabel('Time Point'); -% ylabel('Response'); -% title(['Mean Response Over Time with WSE for Condition ', num2str(c)]); -% end -% -% -% -% barplot_get_within_ste(models3d(:,:,1)) -% barplot_get_within_ste(models3d(:,:,2)) -% barplot_get_within_ste(models3d(:,:,3)) - diff --git a/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/animate.m b/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/animate.m deleted file mode 100644 index d6cbd2bb..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/animate.m +++ /dev/null @@ -1,256 +0,0 @@ -function animate(data, tr, varargin) - % Helper script to create condition vectors for hrf_fit_one_voxel() - % Michael Sun, Ph.D. - % - Takes fmri_data() object or the number of TRs in a 4D object. - % - conditions: cell-vector of cellstrings for each condition e.g., {'hot', 'warm', 'imagine'} - % - onsets: SPM-style onsets cell array in seconds from first-level model, e.g., {{1,2,3}, {4, 5, 6}, {7, 8, 9}} - % - duration: SPM-style duration cell array in seconds from first-level model, e.g., {{12, 12 ,12}, {12, 12, 12}, {12, 12, 12}} - % - % *Usage: - % :: - % Condition = generateConditionTS(image_obj, {'hot','warm','imagine'}, onsets, durations}) - % - % Note 1: Preset a SPIKES or SPIKETRAINS variable in order to toggle the - % generation of Spikes (single 1s) or Spiketrains (a train of 1s) to - % represent each event. - % - % Note 2: If there was slice-timing correction performed, then you will - % want to correct for it by adding 0.5TRs to all of your times. - - % Default to modeling single spike instead of duration of events. - - % Validate input arguments - -% if nargin < 4 -% error('You must provide fmri_data, tr, outfile name, and outtype "avi" or "gif"'); -% end - - if numel(varargin)==1 - outfile=varargin{1}; - outtype='avi'; - elseif numel(varargin)==2 - outfile=varargin{1}; - outtype=varargin{2}; - else - outfile='animated_file.avi'; - outtype='avi'; - end - - % Use fileparts to split the path - [parentFolder, ~, ~] = fileparts(outfile); - - % Check if the folder exists - if ~exist(parentFolder, 'dir') - % Folder does not exist, so create it - mkdir(parentFolder); - disp('Folder was created.'); - end - - - if strcmp(outtype,'avi') - makeFmriAvi(data, tr, outfile); - elseif strcmp(outtype, 'gif') - makeFmriGif(data, tr, outfile); - else - disp([outtype, ' is not supported yet.']) - end - -end - -% function makeFmriAvi(data, tr, outfile) -% % data: fmri_data object -% % varargin: -% % 'TR', numeric -% % 'outfile', cellstr -% % '', -% -% -% % Define the animation parameters -% -% frame_rate = 1/tr; % e.g., 2.17; for a TR of 0.46 -% -% % % Create a movie object -% % writerObj = VideoWriter(outfile); -% % writerObj.FrameRate = frame_rate; -% % open(writerObj); -% % -% % % Create a loop that iterates over each time point in the fMRI data -% % for i = 1:size(data.dat,2) -% % % Replace with preferred plotting logic using 'data'. -% % get_wh_image(data, i).montage('full', 'cmaprange', [-7 7]); -% % -% % % Capture the image as a frame in the movie object -% % frame = getframe(gcf); -% % writeVideo(writerObj,frame); -% % close; -% % end -% -% % Create a movie object -% writerObj = VideoWriter(outfile); -% writerObj.FrameRate = frame_rate; -% open(writerObj); -% -% % Preallocate figure and axes -% % fig = figure; -% % ax = axes('Parent', fig); -% % -% % % Process each frame -% % parfor i = 1:size(data.dat,2) -% % % Update plot in the preallocated figure -% % cla(ax); % Clear axes -% % get_wh_image(data, i).montage('full', 'cmaprange', [-7 7], 'Parent', ax); -% % -% % % Capture the image as a frame in the movie object -% % frame = getframe(fig); -% % writeVideo(writerObj, frame); -% % end -% -% -% % Parallel generation of frames with index -% % Parallel Approach -% nFrames = size(data.dat,2); -% frames(nFrames) = struct('cdata',[],'colormap',[]); % Preallocate structure array -% -% parfor i = 1:nFrames -% frames(i).data = frameCapture(data, i); -% frames(i).index = i; % Store the index -% end -% -% % Sort frames based on index to ensure correct order -% [~, order] = sort([frames.index]); -% sortedFrames = frames(order); -% -% % Sequentially write sorted frames to video -% for i = 1:nFrames -% writeVideo(writerObj, sortedFrames(i).data); -% end -% -% -% -% % Close the movie object -% close(writerObj); -% % close(fig); -% -% end - -function makeFmriAvi(data, tr, outfile) - - - - - frame_rate = 1/tr; - writerObj = VideoWriter(outfile); - writerObj.FrameRate = frame_rate; - open(writerObj); - - nFrames = size(data.dat,2); - tempDir = tempname; % Create a temporary directory name - mkdir(tempDir); % Make the temporary directory - - % Parallel generation of frames - parfor i = 1:nFrames - frameFilename = fullfile(tempDir, sprintf('frame_%06d.png', i)); - frame = frameCapture(data, i); - imwrite(frame.cdata, frameFilename); % Write frame to file - end - - % Sequentially read frames from files and write to video - for i = 1:nFrames - frameFilename = fullfile(tempDir, sprintf('frame_%06d.png', i)); - if exist(frameFilename, 'file') - frame = imread(frameFilename); - writeVideo(writerObj, frame); - else - error(['Frame file missing: ', frameFilename]); - end - end - - % Close the movie object - close(writerObj); - - % Cleanup: Delete the temporary frames - rmdir(tempDir, 's'); % Remove the directory and its contents -end - - - - -function makeFmriGif(data, fr, outfile) - % data: fmri_data object or appropriate data for the animation - % fr: frame rate - % outfile: output file name - - % Ensure that 'data' contains valid information for the animation. - % Ensure that 'outfile' is a string and ends with '.gif'. - - % Number of frames based on the data - numFrames = size(data.dat,2); - mov(numFrames) = struct('cdata',[],'colormap',[]); - - % Loop through each frame of the data - for i = 1:numFrames - % Replace with preferred plotting logic using 'data'. - get_wh_image(data, i).montage('full', 'cmaprange', [-7 7]); - - drawnow - % Capture the current plot as a movie frame - mov(i) = getframe(gcf); - close; - end - - % Convert the movie frames to indexed images - [imind, cm] = rgb2ind(mov(1).cdata, 256, 'nodither'); - imind(1,1,1,numFrames) = 0; - for i = 1:numFrames - imind(:,:,1,i) = rgb2ind(mov(i).cdata, cm, 'nodither'); - end - - % Save the indexed images as an animated gif - delay = 1/fr; % delay between frames in seconds - loopcount = inf; % number of times to repeat animation (inf = indefinitely) - imwrite(imind, cm, outfile, 'gif', 'DelayTime', delay, 'LoopCount', loopcount); -end - - -function frame = frameCapture(data, i) - - % Call montage, which creates its own figure - get_wh_image(data, i).montage('full', 'cmaprange', [-7 7], 'noverbose'); - - % Find the most recently created figure - figs = findall(groot, 'Type', 'figure'); - latestFig = figs(end); - - % Make the figure visible and set the size - set(latestFig, 'Position', [100, 100, 2560, 1080], 'Visible', 'on'); - - drawnow; snapnow; - - % Dynamic waiting for figure to update -% waitTime = 0; -% maxWaitTime = 400; % Slightly longer than your longest frame processing time -% while waitTime < maxWaitTime -% drawnow; snapnow; % Update figure window -% pause(5); % Check every 5 seconds -% waitTime = waitTime + 5; -% % Add any additional checks here if possible to confirm rendering is complete -% end - - % Capture the frame from the latest figure - frame = getframe(latestFig); - - % Close the latest figure - close(latestFig); - - -end - - - - - - - - - - diff --git a/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/describeHRF.m b/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/describeHRF.m deleted file mode 100644 index 4c571588..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/describeHRF.m +++ /dev/null @@ -1,30 +0,0 @@ -function describeHRF(HRF) - % Generates a description from an HRF Structure. - % Michael Sun, Ph.D. - % - Takes HRF Structure object generated from EstimateHRF_inAtlas() - % - % *Usage: - % :: - % describeHRF(HRF_structure) - - for c = 1:numel(HRF.CondNames) - for t = 1:numel(HRF.types) - for r = 1:numel(HRF.region) - - disp(['The ', HRF.types{t}, ' fitted ', HRF.region{r}, ' features ', num2str(numel(HRF.fit{t}{r}.(HRF.CondNames{c}).phases)), ' phases of activity for the ', HRF.CondNames{c}, ' condition']); - for p = 1:numel(HRF.fit{t}{r}.(HRF.CondNames{c}).phases) - if HRF.fit{t}{r}.(HRF.CondNames{c}).phase(p).peaks>0 - disp(['Phase ' num2str(p), ', spanning TRs ', num2str(HRF.fit{t}{r}.(HRF.CondNames{c}).phases{p}), ' features ', num2str(HRF.fit{t}{r}.(HRF.CondNames{c}).phase(p).peaks), ' peak(s), (at TR(s) ', num2str(HRF.fit{t}{r}.(HRF.CondNames{c}).phase(p).time_to_peak), ... - ' and features an AUC of ' num2str(HRF.fit{t}{r}.(HRF.CondNames{c}).phase(p).auc), ' (voxel-normed: ', num2str(HRF.fit{t}{r}.(HRF.CondNames{c}).phase(p).auc_voxnormed), ').']); - end - if HRF.fit{t}{r}.(HRF.CondNames{c}).phase(p).troughs>0 - disp(['Phase ' num2str(p), ', spanning TRs ', num2str(HRF.fit{t}{r}.(HRF.CondNames{c}).phases{p}), ' features ', num2str(HRF.fit{t}{r}.(HRF.CondNames{c}).phase(p).troughs), ' trough(s), (at TR(s) ', num2str(HRF.fit{t}{r}.(HRF.CondNames{c}).phase(p).time_to_peak), ... - ' and features an AUC of ' num2str(HRF.fit{t}{r}.(HRF.CondNames{c}).phase(p).auc), ' (voxel-normed: ', num2str(HRF.fit{t}{r}.(HRF.CondNames{c}).phase(p).auc_voxnormed), ').']); - end - end - - end - disp(newline); - end - end -end \ No newline at end of file diff --git a/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/detectPeaksTroughs.m b/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/detectPeaksTroughs.m deleted file mode 100644 index 50fec016..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/detectPeaksTroughs.m +++ /dev/null @@ -1,130 +0,0 @@ -function [peaks, troughs] = detectPeaksTroughs(waveform, shouldPlot) - % Detect Peaks and Troughs from a waveform using Matlab Signal - % Processing Toolbox - % Michael Sun, Ph.D. - % - Takes a vector timeseries e.g., generated from EstimateHRF_inAtlas - % or hrf_fit_one_voxel(). - % - shouldPlot: 1: yes, plot; 0: no, don't plot - % - % *Usage: - % :: - % [peaks, troughs] = detectPeaksTroughs(waveform, 1) - - % [tc, roi_val, maskdat]=canlab_connectivity_preproc(fmri_d, PREPROC_PARAMS.R, 'hpf', .018, PREPROC_PARAMS.TR, 'average_over', 'no_plots'); - - if nargin < 2 - shouldPlot = false; - end - - % Initialize - peaks = struct([]); - troughs = struct([]); - auc_values = []; - - - % Detecting Peaks: - % [d_pos, l_pos, pos_width, pos_prominences] = findpeaks(waveform, 'MinPeakHeight', waveform(1), 'MinPeakDistance', 1); - % threshold_prominence = 0.5 * max(pos_prominences); - % - % % Detecting Troughs: - % [d_neg, l_neg, neg_width, neg_prominences] = findpeaks(-waveform, 'MinPeakHeight', waveform(1), 'MinPeakDistance', 1); - % threshold_prominence = 0.5 * max(neg_prominences); - - % Detecting Peaks: - [d_pos, l_pos, ~, pos_prominences] = findpeaks(waveform, 'MinPeakHeight', waveform(1), 'MinPeakDistance', 1); - if d_pos > 0 - peak_intervals=diff(l_pos); - threshold_prominence = .5*max(pos_prominences); - [d_pos, l_pos, pos_width, pos_prominences] = findpeaks(waveform, 'MinPeakHeight', waveform(1), 'MinPeakDistance', 1, 'MinPeakProminence', threshold_prominence, 'Annotate', 'extents'); - end - - % Detecting Troughs - [d_neg, l_neg, ~, neg_prominences] = findpeaks(-waveform, 'MinPeakHeight', waveform(1), 'MinPeakDistance', 1); - if d_neg > 0 - trough_intervals=diff(l_neg); - threshold_prominence = .5*max(neg_prominences); - [d_neg, l_neg, ~, neg_prominences] = findpeaks(-waveform, 'MinPeakHeight', waveform(1), 'MinPeakDistance', 1, 'MinPeakProminence', threshold_prominence, 'Annotate', 'extents'); - end - - % fields = {'height', 'time_to_peak', 'width', 'start_time', 'end_time', 'half_height', 'AUC'}; - % Initialize with default values - default_struct = struct('height', NaN, 'time_to_peak', NaN, 'width', NaN, 'start_time', NaN, 'end_time', NaN, 'half_height', NaN, 'AUC', NaN); - peaks = repmat(default_struct, 1, length(l_pos)); - troughs = repmat(default_struct, 1, length(l_neg)); - - % Optional Plotting - if shouldPlot - % figure; - plot(waveform); - hold on; - end - - % Process Peaks - for i = 1:length(l_pos) - [start_point, end_point, auc] = processRegion(l_pos(i), waveform, d_pos(i), shouldPlot, 'ro', [1, 0.6, 0.6], false); - peaks(i) = storeFeatures(d_pos(i), l_pos(i), start_point, end_point, auc); - end - - % Process Troughs - for j = 1:length(l_neg) - [start_point, end_point, auc] = processRegion(l_neg(j), -waveform, d_neg(j), shouldPlot, 'bo', [0.6, 0.6, 1], true); - troughs(j) = storeFeatures(-d_neg(j), l_neg(j), start_point, end_point, auc); - end - - % hold off; - -end - -%% HELPER FUNCTIONS - -function [start_point, end_point, auc] = processRegion(loc, waveform, height, shouldPlot, marker, fillColor, isTrough) - start_point = find(waveform(1:loc) <= 0, 1, 'last') + 1; - if isempty(start_point) - start_point = 1; - end - - end_point = find(waveform(loc:end) <= 0, 1, 'first') + loc - 2; - if isempty(end_point) - end_point = length(waveform); - end - - % Ensure start_point is less than end_point - if start_point > end_point - temp = start_point; - start_point = end_point; - end_point = temp; - end - - region_data = waveform(start_point:end_point); - auc = abs(trapz(region_data)); - - if shouldPlot - x = start_point:end_point; - y = waveform(x); - x_fill = [x, fliplr(x)]; - y_fill = [y', zeros(1, numel(y))]; - - if isTrough - y_fill = -y_fill; % Flip the y-values for troughs - height = -height; % Make the height negative for troughs - end - - fill(x_fill, y_fill, fillColor, 'EdgeColor', 'none'); - plot(loc, height, marker); - text(loc, height, sprintf('AUC: %.2f', auc)); - end -end - - -function features = storeFeatures(height, time_to_peak, start_time, end_time, auc) - - features.height = height; - features.time_to_peak = time_to_peak; - features.width = end_time - start_time; - features.start_time = start_time; - features.end_time = end_time; - features.half_height = height / 2; - features.AUC = auc; - -end - diff --git a/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/extractHRF.m b/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/extractHRF.m deleted file mode 100644 index c86523f8..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/extractHRF.m +++ /dev/null @@ -1,256 +0,0 @@ -function [HRF, tc] = extractHRF(HRF_OBJ, CondNames, varargin) - % Passed in HRF_OBJ is a cell array of fmri_data for each condition. - % CondNames should be a cell array of charstr for each condition - - isSigFound = false; - isAtlasFound = false; - isRegsFound = false; - r = []; - s = []; - - if isa(HRF_OBJ, 'fmri_data') - HRF_OBJ={HRF_OBJ}; - end - - - % Initialize the parallel pool if it's not already running - % if isempty(gcp('nocreate')) - % parpool; - % end - - for k = 1:length(varargin) - if strcmpi(varargin{k}, 'atlas') - if isa(varargin{k+1}, 'atlas') - at=varargin{k+1}; - else - error('Passed in atlas not an atlas.'); - end - - isAtlasFound = true; - end - - if strcmpi(varargin{k}, 'regions') - if isAtlasFound - if ischar(varargin{k+1}) - r={varargin{k+1}}; - isRegsFound = true; - elseif iscell(varargin{k+1}) - r=varargin{k+1}; - isRegsFound = true; - end - else - error('Cannot extract HRF of regions without atlas') - end - end - - if isAtlasFound && ~isRegsFound - % Extract all atlas regions. - r=at.labels; - end - - if strcmpi(varargin{k}, 'sig') % Expected input: 'nps', 'siips', or 'all' - if ischar(varargin{k+1}) - s={varargin{k+1}}; - isSigFound = true; - elseif iscell(varargin{k+1}) - s=varargin{k+1}; - isSigFound = true; - else - error(['Input argument for sig is unknown.']); - end - - end - end - - % ROIs will consist of regions and signatures - rois = [r, s]; - - % Preallocation of tc_local before the parfor loop - HRF= cell(1, numel(rois)); - tc= cell(1, numel(rois)); - - CondNames=matlab.lang.makeValidName(CondNames); - - % Now, handle the fourth dimension which varies with 'd' - numCondNames = numel(CondNames); - - % HRF_local{d} = cell(1, numel(rois)); - % tc_local{d} = cell(1, numel(rois)); - - % Consider doing apply_parcellation instead of mean(apply_mask(HRF_OBJ{c}, at.select_atlas_subset(rois(r), 'exact')).dat); - % [parcel_means, parcel_pattern_expression, parcel_valence, rmsv_pos, rmsv_neg] = apply_parcellation(dat,at); - % nps=load_image_set('npsplus'); - % nps = get_wh_image(nps,1); - % [parcel_means, parcel_pattern_expression, parcel_valence, rmsv_pos, rmsv_neg] = apply_parcellation(dat,at, 'pattern_expression', nps); - % r=region(at,'unique_mask_values'); - % wh_parcels=~all(isnan(parcel_means)) - - for r=1:numel(rois) - HRF{r} = struct; - tc{r} = cell(1, numCondNames); - - - tic - if isSigFound & strcmpi(rois{r}, 'all') - [sig_dp, ~]=apply_all_signatures(HRF_OBJ, 'similarity_metric', 'dot_product', 'conditionnames', CondNames, 'image_set', 'all'); - [sig_cs, ~]=apply_all_signatures(HRF_OBJ, 'similarity_metric', 'cosine_similarity', 'conditionnames', CondNames, 'image_set', 'all'); - [sig_r, ~]=apply_all_signatures(HRF_OBJ, 'similarity_metric', 'correlation', 'conditionnames', CondNames, 'image_set', 'all'); - end - - - for c=1:numel(CondNames) - - % Check to see if its a signature name first - if isSigFound - - switch rois{r} - case 'all' - for sig = 1:numel(sig_dp.signaturenames) - try - HRF{r}.([CondNames{c},'_',sig_dp.signaturenames{sig},'_dp']).model=sig_dp.(sig_dp.signaturenames{sig}).(CondNames{c}); - HRF{r}.([CondNames{c},'_',sig_cs.signaturenames{sig},'_cs']).model=sig_cs.(sig_cs.signaturenames{sig}).(CondNames{c}); - HRF{r}.([CondNames{c},'_',sig_r.signaturenames{sig},'_r']).model=sig_r.(sig_r.signaturenames{sig}).(CondNames{c}); - - [HRF{r}.([CondNames{c},'_',sig_dp.signaturenames{sig},'_dp']).peaks, HRF{r}.([CondNames{c},'_',sig_dp.signaturenames{sig},'_dp']).troughs]=detectPeaksTroughs(sig_dp.(sig_dp.signaturenames{sig}).(CondNames{c}), false); - [HRF{r}.([CondNames{c},'_',sig_cs.signaturenames{sig},'_cs']).peaks, HRF{r}.([CondNames{c},'_',sig_cs.signaturenames{sig},'_cs']).troughs]=detectPeaksTroughs(sig_cs.(sig_cs.signaturenames{sig}).(CondNames{c}), false); - [HRF{r}.([CondNames{c},'_',sig_r.signaturenames{sig},'_r']).peaks, HRF{r}.([CondNames{c},'_',sig_r.signaturenames{sig},'_r']).troughs]=detectPeaksTroughs(sig_r.(sig_r.signaturenames{sig}).(CondNames{c}), false); - - catch - disp(c); - disp(rois{r}); - end - end - - case 'nps' - % tc{r}=apply_nps(HRF_OBJ); % This does all conditions at once! - try - % tc{r}{c}=apply_nps(HRF_OBJ{c}); - HRF{r}.([CondNames{c}, '_NPS_dp']).model=cell2mat(apply_nps(HRF_OBJ{c})); - HRF{r}.([CondNames{c}, '_NPS_cs']).model=cell2mat(apply_nps(HRF_OBJ{c}, 'cosine_similarity')); - HRF{r}.([CondNames{c}, '_NPS_r']).model=cell2mat(apply_nps(HRF_OBJ{c}, 'correlation')); - [HRF{r}.([CondNames{c}, '_NPS_dp']).peaks, HRF{r}.([CondNames{c}, '_NPS_dp']).troughs]=detectPeaksTroughs(HRF{r}.([CondNames{c}, '_NPS_dp']).model, false); - [HRF{r}.([CondNames{c}, '_NPS_cs']).peaks, HRF{r}.([CondNames{c}, '_NPS_cs']).troughs]=detectPeaksTroughs( HRF{r}.([CondNames{c}, '_NPS_cs']).model, false); - [HRF{r}.([CondNames{c}, '_NPS_r']).peaks, HRF{r}.([CondNames{c}, '_NPS_r']).troughs]=detectPeaksTroughs(HRF{r}.([CondNames{c}, '_NPS_r']).model, false); - catch - disp(c); - disp(rois{r}); - end - - case 'siips' - try - HRF{r}.([CondNames{c}, '_SIIPS_dp']).model=cell2mat(apply_siips(HRF_OBJ{c})); - HRF{r}.([CondNames{c}, '_SIIPS_cs']).model=cell2mat(apply_siips(HRF_OBJ{c}, 'cosine_similarity')); - HRF{r}.([CondNames{c}, '_SIIPS_r']).model=cell2mat(apply_siips(HRF_OBJ{c}, 'correlation')); - [HRF{r}.([CondNames{c}, '_SIIPS_dp']).peaks, HRF{r}.([CondNames{c}, '_SIIPS_dp']).troughs]=detectPeaksTroughs(HRF{r}.([CondNames{c}, '_SIIPS_dp']).model, false); - [HRF{r}.([CondNames{c}, '_SIIPS_cs']).peaks, HRF{r}.([CondNames{c}, '_SIIPS_cs']).troughs]=detectPeaksTroughs( HRF{r}.([CondNames{c}, '_SIIPS_cs']).model, false); - [HRF{r}.([CondNames{c}, '_SIIPS_r']).peaks, HRF{r}.([CondNames{c}, '_SIIPS_r']).troughs]=detectPeaksTroughs(HRF{r}.([CondNames{c}, '_SIIPS_r']).model, false); - catch - disp(c); - disp(rois{r}); - end - end - end - - - if isAtlasFound & ~ismember(rois{r}, {'all', 'nps', 'siips'}) - - - try - tc{r}{c}=mean(apply_mask(HRF_OBJ{c}, at.select_atlas_subset(rois(r), 'exact')).dat); - - HRF{r}.(CondNames{c}).model=tc{r}{c}; - [HRF{r}.(CondNames{c}).peaks, HRF{r}.(CondNames{c}).troughs]=detectPeaksTroughs(tc{r}{c}', false); - - [~, regionVoxNum, ~, ~]=at.select_atlas_subset(rois(r), 'exact').get_region_volumes; - HRF{r}.(CondNames{c}).model_voxnormed=tc{r}{c}/regionVoxNum; - [HRF{r}.(CondNames{c}).peaks_voxnormed, HRF{r}.(CondNames{c}).troughs_voxnormed]=detectPeaksTroughs(tc{r}{c}'/regionVoxNum, false); - catch - error("Error generating Peaks and Troughs. Check if Matlab Signal Processing Toolbox is Installed.") - % disp(t); - disp(c); - disp(rois{r}); - tc{r}{c} - mean(apply_mask(HRF_OBJ{c}, at.select_atlas_subset(rois(r), 'exact')).dat) - {apply_mask(HRF_OBJ{c}, at.select_atlas_subset(rois(r), 'exact')).dat} - HRF_OBJ - - end - - % Number of phases - start_times = [HRF{r}.(CondNames{c}).peaks.start_time, HRF{r}.(CondNames{c}).troughs.start_time]; - end_times = [HRF{r}.(CondNames{c}).peaks.end_time, HRF{r}.(CondNames{c}).troughs.end_time]; - phases = [start_times(:), end_times(:)]; - unique_phases = unique(phases, 'rows'); - HRF{r}.(CondNames{c}).phases = mat2cell(unique_phases, ones(size(unique_phases, 1), 1), 2); - - % Loop over phases - for p = 1:numel(HRF{r}.(CondNames{c}).phases) - features = {'peaks', 'troughs'}; - for f = 1:2 - feat = features{f}; - - % Count the number of features (peaks or troughs) - start_times = cell2mat({HRF{r}.(CondNames{c}).(feat).start_time}); - current_phase_start = HRF{r}.(CondNames{c}).phases{p}(1); - HRF{r}.(CondNames{c}).phase(p).(feat) = sum(start_times == current_phase_start); - - if HRF{r}.(CondNames{c}).phase(p).(feat) > 0 - display(['Estimating ' , '_', rois{r}, '_', CondNames{c}, '_', ' Now...!']); - display(['Phase ' , num2str(p), 'Feature ', feat]); - idx = find(start_times == current_phase_start); - auc = cell2mat({HRF{r}.(CondNames{c}).(feat).AUC}); - height = cell2mat({HRF{r}.(CondNames{c}).(feat).height}); - time_to_peak = cell2mat({HRF{r}.(CondNames{c}).(feat).time_to_peak}); - half_height = cell2mat({HRF{r}.(CondNames{c}).(feat).half_height}); - - feat_voxnormed = strcat(feat, '_voxnormed'); - auc_voxnormed = cell2mat({HRF{r}.(CondNames{c}).(feat_voxnormed).AUC}); - height_voxnormed = cell2mat({HRF{r}.(CondNames{c}).(feat_voxnormed).height}); - time_to_peak_voxnormed = cell2mat({HRF{r}.(CondNames{c}).(feat_voxnormed).time_to_peak}); - half_height_voxnormed = cell2mat({HRF{r}.(CondNames{c}).(feat_voxnormed).half_height}); - - HRF{r}.(CondNames{c}).phase(p).auc = unique(auc(idx)); - HRF{r}.(CondNames{c}).phase(p).auc_voxnormed = unique(auc_voxnormed(idx)); - - HRF{r}.(CondNames{c}).phase(p).height = height(idx); - HRF{r}.(CondNames{c}).phase(p).height_voxnormed = height_voxnormed(idx); - HRF{r}.(CondNames{c}).phase(p).time_to_peak = time_to_peak(idx); - HRF{r}.(CondNames{c}).phase(p).time_to_peak_voxnormed = time_to_peak_voxnormed(idx); - HRF{r}.(CondNames{c}).phase(p).half_height = half_height(idx); - HRF{r}.(CondNames{c}).phase(p).half_height_voxnormed = half_height_voxnormed(idx); - end - end - end - end - display(strjoin({' Done in ', num2str(toc), ' seconds with ', rois{r}})); - end - - end - - % HRF_struct=struct; - % if isAtlasFound - % HRF_struct.atlas=at; - % end - % HRF_struct.region=rois; - % HRF_struct.CondNames=CondNames; - % HRF_struct.fit=HRF; - % HRF=HRF_struct; - - - - % temp_HRF_fit = HRF_local; - % Save the results for this ROI - % display([num2str(t), ' Done!']) - % temp_HRF_fit{t} = HRF_local; - % tc{t} = tc; - - - % Transfer the results from the temporary cell array to the HRF structure - % HRF.fit = temp_HRF_fit; - % HRF.params=HRF_PARAMS; - - % delete(gcp('nocreate')); - - - -end \ No newline at end of file diff --git a/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/fitHRF_batch.m b/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/fitHRF_batch.m deleted file mode 100644 index bc4df6e0..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/fitHRF_batch.m +++ /dev/null @@ -1,116 +0,0 @@ -function [tc, HRF]=fitHRF_batch(datadir, HRF_PARAMS, rois, at, outfile) - % This is not done yet. - - % Make directories for files if needed - if ~isempty(fileparts(outfile)) - if ~exist(fileparts(outfile), 'dir') - mkdir(fileparts(outfile)); - end - end - - HRF.atlas=at.atlas_name; - HRF.region=rois; - HRF.types=HRF_PARAMS.types; - HRF.name=fmri_d.image_names; - - % Initialize 'tc' and 'temp_HRF_fit' cell arrays - tc = cell(1, numel(HRF_PARAMS.types); - temp_HRF_fit = cell(1, numel(HRF_PARAMS.types)); - - % Write out the images for later post-analyses - % [~, fname, ~]=fileparts(preproc_dat.image_names); - % fname=outfile - - HRF_local = cell(1, numel(HRF_PARAMS.types)); - tc_local = cell(1, numel(rois)); - - - canlab_list_subjects() - - fmri_data() - - - - for r=1:numel(rois) - tic - for c=1:numel(HRF_PARAMS.CondNames) - - try - tc_local{r}{c}=mean(apply_mask(HRF_OBJ{c}, at.select_atlas_subset(rois(r), 'exact')).dat); - - HRF_local{r}.(HRF_PARAMS.CondNames{c}).model=tc_local{r}{c}; - [HRF_local{r}.(HRF_PARAMS.CondNames{c}).peaks, HRF_local{r}.(HRF_PARAMS.CondNames{c}).troughs]=detectPeaksTroughs(tc_local{r}{c}', false); - - [~, regionVoxNum, ~, ~]=at.select_atlas_subset(rois(r), 'exact').get_region_volumes; - HRF_local{r}.(HRF_PARAMS.CondNames{c}).model_voxnormed=tc_local{r}/regionVoxNum; - [HRF_local{r}.(HRF_PARAMS.CondNames{c}).peaks_voxnormed, HRF_local{r}.(HRF_PARAMS.CondNames{c}).troughs_voxnormed]=detectPeaksTroughs(tc_local{r}{c}'/regionVoxNum, false); - catch - disp(t); - disp(c); - disp(rois{r}); - tc_local{r}{c} - mean(apply_mask(HRF_OBJ{c}, at.select_atlas_subset(rois(r), 'exact')).dat) - {apply_mask(HRF_OBJ{c}, at.select_atlas_subset(rois(r), 'exact')).dat} - HRF_OBJ{c} - - end - - % Number of phases - start_times = [HRF_local{r}.(HRF_PARAMS.CondNames{c}).peaks.start_time, HRF_local{r}.(HRF_PARAMS.CondNames{c}).troughs.start_time]; - end_times = [HRF_local{r}.(HRF_PARAMS.CondNames{c}).peaks.end_time, HRF_local{r}.(HRF_PARAMS.CondNames{c}).troughs.end_time]; - phases = [start_times(:), end_times(:)]; - unique_phases = unique(phases, 'rows'); - HRF_local{r}.(HRF_PARAMS.CondNames{c}).phases = mat2cell(unique_phases, ones(size(unique_phases, 1), 1), 2); - - % Loop over phases - for p = 1:numel(HRF_local{r}.(HRF_PARAMS.CondNames{c}).phases) - features = {'peaks', 'troughs'}; - for f = 1:2 - feat = features{f}; - - % Count the number of features (peaks or troughs) - start_times = cell2mat({HRF_local{r}.(HRF_PARAMS.CondNames{c}).(feat).start_time}); - current_phase_start = HRF_local{r}.(HRF_PARAMS.CondNames{c}).phases{p}(1); - HRF_local{r}.(HRF_PARAMS.CondNames{c}).phase(p).(feat) = sum(start_times == current_phase_start); - - if HRF_local{r}.(HRF_PARAMS.CondNames{c}).phase(p).(feat) > 0 - display(['Estimating ' , num2str(t), '_', rois{r}, '_', HRF_PARAMS.CondNames{c}, '_', ' Now...!']) - display(['Phase ' , num2str(p), 'Feature ', feat]) - idx = find(start_times == current_phase_start) - auc = cell2mat({HRF_local{r}.(HRF_PARAMS.CondNames{c}).(feat).AUC}) - height = cell2mat({HRF_local{r}.(HRF_PARAMS.CondNames{c}).(feat).height}) - time_to_peak = cell2mat({HRF_local{r}.(HRF_PARAMS.CondNames{c}).(feat).time_to_peak}) - half_height = cell2mat({HRF_local{r}.(HRF_PARAMS.CondNames{c}).(feat).half_height}) - - feat_voxnormed = strcat(feat, '_voxnormed') - auc_voxnormed = cell2mat({HRF_local{r}.(HRF_PARAMS.CondNames{c}).(feat_voxnormed).AUC}) - height_voxnormed = cell2mat({HRF_local{r}.(HRF_PARAMS.CondNames{c}).(feat_voxnormed).height}) - time_to_peak_voxnormed = cell2mat({HRF_local{r}.(HRF_PARAMS.CondNames{c}).(feat_voxnormed).time_to_peak}) - half_height_voxnormed = cell2mat({HRF_local{r}.(HRF_PARAMS.CondNames{c}).(feat_voxnormed).half_height}) - - HRF_local{r}.(HRF_PARAMS.CondNames{c}).phase(p).auc = unique(auc(idx)) - HRF_local{r}.(HRF_PARAMS.CondNames{c}).phase(p).auc_voxnormed = unique(auc_voxnormed(idx)) - - HRF_local{r}.(HRF_PARAMS.CondNames{c}).phase(p).height = height(idx) - HRF_local{r}.(HRF_PARAMS.CondNames{c}).phase(p).height_voxnormed = height_voxnormed(idx) - HRF_local{r}.(HRF_PARAMS.CondNames{c}).phase(p).time_to_peak = time_to_peak(idx) - HRF_local{r}.(HRF_PARAMS.CondNames{c}).phase(p).time_to_peak_voxnormed = time_to_peak_voxnormed(idx) - HRF_local{r}.(HRF_PARAMS.CondNames{c}).phase(p).half_height = half_height(idx) - HRF_local{r}.(HRF_PARAMS.CondNames{c}).phase(p).half_height_voxnormed = half_height_voxnormed(idx) - end - end - end - end - display([num2str(t), ' Done in ', toc, ' with ' rois(r)]); - - end - % Save the results for this ROI - display([num2str(t), ' Done!']) - temp_HRF_fit{t} = HRF_local; - tc{t} = tc_local; -end - -% Transfer the results from the temporary cell array to the HRF structure -HRF.fit = temp_HRF_fit; -HRF.params=HRF_PARAMS; -end \ No newline at end of file diff --git a/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/generateConditionTS.m b/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/generateConditionTS.m deleted file mode 100644 index ab08cfba..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/generateConditionTS.m +++ /dev/null @@ -1,75 +0,0 @@ -function Condition=generateConditionTS(fmri_d, conditions, onsets, durations, TR, varargin) - % Helper script to create condition vectors for hrf_fit_one_voxel() - % Michael Sun, Ph.D. - % - Takes fmri_data() object or the number of TRs in a 4D object. - % - conditions: cell-vector of cellstrings for each condition e.g., {'hot', 'warm', 'imagine'} - % - onsets: SPM-style onsets cell array in seconds from first-level model, e.g., {{1,2,3}, {4, 5, 6}, {7, 8, 9}} - % - duration: SPM-style duration cell array in seconds from first-level model, e.g., {{12, 12 ,12}, {12, 12, 12}, {12, 12, 12}} - % - % *Usage: - % :: - % Condition = generateConditionTS(image_obj, {'hot','warm','imagine'}, onsets, durations}) - % - % Note 1: Preset a SPIKES or SPIKETRAINS variable in order to toggle the - % generation of Spikes (single 1s) or Spiketrains (a train of 1s) to - % represent each event. - % - % Note 2: If there was slice-timing correction performed, then you will - % want to correct for it by adding 0.5TRs to all of your times. - - % Default to modeling single spike instead of duration of events. - if ~exist('SPIKES', 'var') && ~exist('SPIKETRAINS', 'var') - SPIKES=1; - SPIKETRAINS=0; - end - - if ~isempty(varargin) - SPIKETRAINS=1; - else - SPIKES=1; - end - - if strcmp(class(fmri_d), 'fmri_data') - n=size(fmri_d.dat,2); - elseif isnumeric(fmri_d) - n=fmri_d; - end - - - % Number of conditions - nconds = length(conditions); - - % Initialize the Condition cell array - for c = 1:nconds - Condition{c} = zeros(n,1); - end - - % Loop through each condition to process their times and update Condition - for c = 1:nconds - % Extract the times for the current condition - % current_times = eval([conditions{c} '_times{sub}{j}']); - current_times=onsets{c}/TR; - current_dur=durations{c}/TR; - - % Remove the onset times that didn't get recorded. - % Note: Add 0.5TRs to correct for slice-timing correction if needed - % current_times = ceil(current_times(current_times < n) + 0.5); - current_times = ceil(current_times(current_times < n)); - % Correct any onsets that start at 0; - if any(current_times==0) - current_times(find(current_times==0))=1; - end - - % Model events as a single impulse - if SPIKES == 1 - Condition{c}(current_times) = 1; - end - - % Model event epochs as trains of events - if SPIKETRAINS == 1 - for i = 1:numel(current_times) - Condition{c}(current_times(i):current_times(i)+(current_dur(i))) = 1; - end - end - end -end \ No newline at end of file diff --git a/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/generateHRFTable.m b/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/generateHRFTable.m deleted file mode 100644 index 2821271a..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/generateHRFTable.m +++ /dev/null @@ -1,66 +0,0 @@ -function T=generateHRFTable(HRF) - % Generates a summary table from an HRF Structure. - % Michael Sun, Ph.D. - % - Takes HRF Structure object generated from EstimateHRF_inAtlas() - % - % *Usage: - % :: - % T=generateHRFTable(HRF_structure) - - % Pre-allocate a cell array to store the table data - tblData = cell(0, 10); % You may need to adjust the number of columns based on the data you're storing - - % Iterate through the nested loops as before - for c = 1:numel(HRF.CondNames) - for r = 1:numel(HRF.region) - for t = 1:numel(HRF.types) - - %disp(['Number of phases: ', num2str(numel(HRF.fit{t}{r}.(HRF.CondNames{c}).phases))]); - %for p = 1:numel(HRF.fit{t}{r}.(HRF.CondNames{c}).phases) - - % Define the basic information - type = HRF.types{t}; - region = HRF.region{r}; - condition = HRF.CondNames{c}; - phase_num = 0; - phase_span = [0 0]; - peaks = 0; - troughs = 0; - time_to_peak = 0; - auc = 0; - auc_voxnormed = 0; - - - if numel(HRF.fit{t}{r}.(HRF.CondNames{c}).phases) > 1 - for p = 1:numel(HRF.fit{t}{r}.(HRF.CondNames{c}).phases) - - phase_num = p; - phase_span = HRF.fit{t}{r}.(HRF.CondNames{c}).phases{p}; - - % Check for peaks and troughs - peaks = HRF.fit{t}{r}.(HRF.CondNames{c}).phase(p).peaks; - troughs = HRF.fit{t}{r}.(HRF.CondNames{c}).phase(p).troughs; - %disp(['Peaks: ', num2str(peaks), ', Troughs: ', num2str(troughs)]); - % Extract other data - time_to_peak = HRF.fit{t}{r}.(HRF.CondNames{c}).phase(p).time_to_peak; - auc = HRF.fit{t}{r}.(HRF.CondNames{c}).phase(p).auc; - auc_voxnormed = HRF.fit{t}{r}.(HRF.CondNames{c}).phase(p).auc_voxnormed; - % Append the data to the cell array - tblData = [tblData; {type, region, condition, phase_num, phase_span, peaks, troughs, time_to_peak, auc, auc_voxnormed}]; - end - else - % Append the data to the cell array - %disp(['c: ', num2str(c), ', t: ', num2str(t), ', r: ', num2str(r)]); - tblData = [tblData; {type, region, condition, phase_num, phase_span, peaks, troughs, time_to_peak, auc, auc_voxnormed}]; - - end - end - end - end - - % Convert the cell array to a table - T = cell2table(tblData, 'VariableNames', {'Type', 'Region', 'Condition', 'PhaseNum', 'PhaseSpan', 'Peaks', 'Troughs', 'TimeToPeak', 'AUC', 'AUC_VoxNormed'}); - - % Display the table - disp(T); -end \ No newline at end of file diff --git a/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/hrf_fit.m b/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/hrf_fit.m deleted file mode 100644 index 23af37cb..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/hrf_fit.m +++ /dev/null @@ -1,506 +0,0 @@ -function [params_obj, hrf_obj, params_obj_dat, hrf_obj_dat, info] = hrf_fit(SPM,T,method,mode) -% HRF estimation on fmri_data class object -% -% HRF estimation function for a single voxel; -% -% Implemented methods include: IL-model (Deterministic/Stochastic), FIR -% (Regular/Smooth), and HRF (Canonical/+ temporal/+ temporal & dispersion). -% With SPM.mat, TR, and experimental design are imported. -% -% :Inputs: -% -% **SPM** -% SPM.mat file -% -% **T** -% length of estimated HRF ij seconds -% -% **type** -% Model type: 'FIR', 'IL', or 'CHRF' -% -% **mode** -% Mode -% -% :Model Types: -% -% A. **Fit HRF using IL-function** -% Choose mode (deterministic/stochastic) -% - 0 - deterministic aproach -% - 1 - simulated annealing approach -% -% Please note that when using simulated annealing approach you -% may need to perform some tuning before use. -% -% B. **Fit HRF using FIR-model** -% Choose mode (FIR/sFIR) -% - 0 - FIR -% - 1 - smooth FIR -% -% C. **Fit Canonical HRF** -% Choose mode (FIR/sFIR) -% - 0 - FIR -% - 1 - smooth FIR -% -% .. -% Created by Michael Sun on 02/20/24 -% .. - -if isstring(SPM) || ischar(SPM) - - load(SPM); - if ~exist('SPM', 'var') - error('Passed in filepath is not an SPM.mat file.') - end - -end - -if strcmp(method, 'sFIR') - method = 'FIR'; - mode = 1; -end - - -if isstruct(SPM) - fnames=unique({SPM.xY.VY.fname})'; - - parfor i=1:numel(fnames) - - if contains(fnames{i}, '/') && ispc - % PC Path conversion from Unix - if strcmp(fnames{i}(1,1:2), '\\') - d{i}=fmri_data(strrep(fnames{i}, '/', '\')); - else - d{i}=fmri_data(['\',strrep(fnames{i}, '/', '\')]); - end - - else - d{i}=fmri_data(fnames{i}); - end - end - - % Transform timeseries data into the way SPM desires. - gkwy_d=spmify(d,SPM); - - % Extract TR - TR = SPM.xY.RT; - - if isempty(T) - - if ~contains(SPM.xBF.name, 'Finite Impulse Response') - % For now, assume 'Canonical HRF' - disp('T is empty, generating T as 2 times the maximum duration for each task regressor.'); - for i = 1:numel(SPM.Sess) - T{i}=cellfun(@(cellArray) 2*ceil(max(cellArray)), {SPM.Sess(i).U.dur}); - end - else - - for i = 1:numel(SPM.Sess) - % T{i}=[SPM.xBF.order]; - % In normal SPM, the time window (length) and resolution (order) is set the same for all regressors. - % T should be in seconds, it gets converted to TRs later. - T{i}=repmat(SPM.xBF.length, size({SPM.Sess(i).U.dur})); - - % T{i}=repmat(SPM.xBF.order*TR, size({SPM.Sess(i).U.dur})); - - end - end - elseif numel(SPM.Sess)>1 && ~iscell(T) - % error('SPM structures concatenating multiple runs must have time windows passed in with a matching number of cell arrays.'); - T=repmat({T}, 1, numel(SPM.Sess)); - elseif iscell(T) && numel(SPM.Sess)~=numel(T) - error('Incompatible number of runs in SPM and in cell-array T.') - end - - % %% ONE WAY - % % run hrf_fit separately for every d - - % Tor: Better to pass in pseudoinverse matrix to speed up computation: - for i=1:numel(d) - % Reassign data. - d{i}.dat=gkwy_d{i}; - - % Extract TR - TR = SPM.xY.RT; - % - Runc=generateConditionTS(numel(SPM.Sess(i).row), [SPM.Sess(i).U.name], {SPM.Sess(i).U.ons}, {SPM.Sess(i).U.dur}, TR); - - % There's a need to pull apart the SPM design matrix for every run - % Column of regressors + covariates for each session + intercept - % X=SPM.xX.X(SPM.Sess(i).row, [SPM.Sess(i).col, SPM.xX.iB(i)]); - - % Trouble is, X is not filtered - % X=spm_filter(SPM.xX.K(i), SPM.xX.X(SPM.Sess(i).row, [SPM.Sess(i).col, SPM.xX.iB(i)])); - - % Trouble here is, task regressors may have to be re-generated for - % FIR and sFIR if the original design matrix is cHRF, so check the - % basis function - if strcmpi(method, 'FIR') && ~contains(SPM.xBF.name, 'Finite Impulse Response') - disp('Passed in SPM structure is not FIR. Reconstructing task-regressors for FIR fit'); - - len = numel(SPM.Sess(i).row); - - % Task regressors for each run can be found here: - numstim=numel(SPM.Sess(i).U); - - % Make the design matrix: - DX_all = cell(1, numstim); % Store DX matrices for each condition - tlen_all = zeros(1, numstim); % Store tlen for each condition - - for s=1:numstim - t = 1:TR:T{i}(s); - tlen_all(s) = length(t); - DX_all{s} = tor_make_deconv_mtx3(Runc(:,s), tlen_all(s), 1); - end - DX = horzcat(DX_all{:}); - - % due to horzcat, we will have multiple intercepts in this design matrix - % therefore, we'll find the intercepts, drop them, and add an intercept at the end of the matrix - intercept_idx = find(sum(DX)==len); - copyDX = DX; - copyDX(:,intercept_idx) = []; - - % Design Matrix Regressors + intercept: - DX = [copyDX ones(len,1)]; - - % Covariate Design Matrix for each session (without intercept): - NX=[SPM.Sess(i).C.C]; - - % Concatenate the Task regressors with Covariates - X=[DX, NX]; - % Filter - % X=spm_filter(SPM.xX.K(i), X); - - % filter is probably performed with spm_sp('Set', xX.K*xX.W*xX.X) - % X=spm_sp('Set', SPM.xX.K(i)*SPM.xX.W*SPM.xX.X) - - % xX.KxXs = spm_sp('Set',spm_filter(xX.K,W*xX.X)); % KWX - - % Ke says preprocess the nuisance regressors first before - % fitting the FIR, otherwise the design matrix has problematic - % VIFs between the FIR indicators and the spike regressors. - % xKNXs = spm_sp('Set',spm_filter(SPM.xX.K(i), SPM.xX.W(SPM.Sess(i).row, SPM.Sess(i).row)*NX)); % KW*Nuisance - % NX = full(xKNXs.X); - % d{i}=canlab_connectivity_preproc(d{i}, 'additional_nuisance', NX, TR,'no_plots') - % preproc_d=canlab_connectivity_preproc(d{i}, 'additional_nuisance', NX) - - % preproc_d=d{i}; - % d{i}.covariates=NX; - % d{i}=preprocess(d{i}, 'resid',1); % Residualize out noise regressors - - % sFIR - if mode == 1 - for s=1:numstim - t = 1:TR:T{i}(s); - tlen_all(s) = length(t); - end - - MRI = zeros(sum(tlen_all)+1); % adjust size based on varying tlen and intercept at end - start_idx = 1; - - for s=1:numstim - tlen = tlen_all(s); % get the tlen for this stimulus - - C = (1:tlen)'*(ones(1,tlen)); - h = sqrt(1/(7/TR)); - - v = 0.1; - sig = 1; - - R = v*exp(-h/2*(C-C').^2); - RI = inv(R); - - % Adjust the indices to account for varying tlen - end_idx = start_idx + tlen - 1; - MRI(start_idx:end_idx, start_idx:end_idx) = RI; - - start_idx = end_idx + 1; % update the starting index for next iteration - end - - % multiply a 0 penalty with NX. - cov_num = size(X,2)-size(MRI,2); % Number of nuisance covariates - pen{1} = sig^2*MRI; % Regularization Penalty Matrix - pen{2} = zeros(cov_num); % for nuisance, add no penalty - pen=blkdiag(pen{:}); - - % disp(pen) % Check what this looks like. - - % Filter everything, and then perform the pseudoinverse - xKXs = spm_sp('Set',spm_filter(SPM.xX.K(i), SPM.xX.W(SPM.Sess(i).row, SPM.Sess(i).row)*X)); % KW*Design - X = full(xKXs.X); - - PX = inv(X'*X+pen)*X'; - % PX = spm_sp('x-', X'*X+pen); % SPM's way of performing - % the pseudoinversion. Need a way to put in in SPM's xKXs - % space structure. - % PX = PX*X' - clear pen; - - else - % Filter and invert - xKXs = spm_sp('Set',spm_filter(SPM.xX.K(i), SPM.xX.W(SPM.Sess(i).row, SPM.Sess(i).row)*X)); % KW*Design - X = full(xKXs.X); - % PX = pinv(X); - PX = spm_sp('x-', xKXs); % SPM's way of performing the pseudoinversion. - % figure, imagesc(X), clim([-.01 .01]) - end - - elseif contains(method, 'CHRF') && ~contains(SPM.xBF.name, 'hrf') - % Problem here is we have onsets but no durations from a - % FIR matrix. If we use FIR - - switch method - case 'CHRF0' - p=1; - case 'CHRF1' - p=2; - case 'CHRF2' - p=3; - end - % Generate a design matrix - Run=Runc; - d = length(Run); - len = length(Run{1}); - - % Constructing the Design Matrix X: - X = zeros(len,p*d); - - [h, dh, dh2] = CanonicalBasisSet(TR); - - for j=1:d, - v = conv(Run{j},h); - X(:,(j-1)*p+1) = v(1:len); - - % Computing the first derivative - if strcmpi(method, 'CHRF1') - v = conv(Run{j},dh); - X(:,(j-1)*p+2) = v(1:len); - end - - % Computing the second derivative - if strcmpi(method, 'CHRF2') - v = conv(Run{j},dh2); - X(:,(j-1)*p+3) = v(1:len); - end - end - else - % Otherwise set X to be the filtered design matrix of that - % section. - - % If using sFIR, pre-penalize the pseudoinverted design matrix - if strcmpi(method, 'FIR') && mode==1 - - % Task regressors for each run can be found here: - numstim=numel(SPM.Sess(i).U); - - % Initialize an array to hold the length of regressors for each task - tlen_all = []; - - % Extract condition names for the session - condition_names = {SPM.Sess(i).U.name}; - - % Loop through condition names to find lengths for each task - for c = 1:length(condition_names) - condition = condition_names{c}{1}; % Adjusted for direct use without {1} - % Find indices of regressors matching this condition - condition_idx = find(contains(SPM.xX.name, ['Sn(', num2str(i), ') ', condition])); - - if ~isempty(condition_idx) - % Calculate the length of this task's block of regressors - task_length = max(condition_idx) - min(condition_idx) + 1; - % Add this length to the tlen_all array - tlen_all(end+1) = task_length; - end - end - - MRI = zeros(sum(tlen_all)+1); % adjust size based on varying tlen and intercept at end - start_idx = 1; - - for s=1:numstim - tlen = tlen_all(s); % get the tlen for this stimulus - - C = (1:tlen)'*(ones(1,tlen)); - h = sqrt(1/(7/TR)); - - v = 0.1; - sig = 1; - - R = v*exp(-h/2*(C-C').^2); - RI = inv(R); - - % Adjust the indices to account for varying tlen - end_idx = start_idx + tlen - 1; - MRI(start_idx:end_idx, start_idx:end_idx) = RI; - - start_idx = end_idx + 1; % update the starting index for next iteration - end - - % Create Penalty Matrix - cov_num = size(SPM.Sess(i).C.C, 2); - pen{1} = sig^2*MRI; % Regularization Penalty Matrix - pen{2} = zeros(cov_num); % for nuisance, add no penalty - pen=blkdiag(pen{:}); - % disp(pen) % Check what this looks like. - - % Invert - X=SPM.xX.xKXs.X(SPM.Sess(i).row, [SPM.Sess(i).col, SPM.xX.iB(i), SPM.xX.iG]); - - % Now penalize SPM's task regressors - PX = inv(X'*X+pen)*X'; - clear pen; - - else - X=SPM.xX.xKXs.X(SPM.Sess(i).row, [SPM.Sess(i).col, SPM.xX.iB(i), SPM.xX.iG]); - % Troubleshooting: - % For some reason the above differs from this: - % X=spm_filter(SPM.xX.K(i), SPM.xX.X(SPM.Sess(i).row, [SPM.Sess(i).col, SPM.xX.iB(i), SPM.xX.iG])); - % X_SPM=SPM.xX.xKXs.X(SPM.Sess(i).row, [SPM.Sess(i).col, SPM.xX.iB(i), SPM.xX.iG]); - % figure, imagesc(X_SPM), clim([-.01 .01]) - % Grab SPM's prefiltered pseudoinverse matrix - PX=SPM.xX.pKX([SPM.Sess(i).col, SPM.xX.iB(i), SPM.xX.iG], SPM.Sess(i).row); - - end - - - end - - % ~ 40 min - % [params_obj{i}, hrf_obj{i}, params_obj_dat{i}, hrf_obj_dat{i}] = hrf_fit(d{i},TR,Runc,T{i},method,mode,X); - % The reason this takes so long is because of the pseudoinversion - % of the design matrix. If we have access to the SPM design matrix, - % we should pass in both DX and its inversion. - - [params_obj{i}, hrf_obj{i}, params_obj_dat{i}, hrf_obj_dat{i}] = hrf_fit(d{i},TR,Runc,T{i},method,mode,X, 'invertedDX', PX); - - - % Test - - % [params_obj1{i}, hrf_obj1{i}, params_obj_dat1{i}, hrf_obj_dat1{i}] = hrf_fit(d{i},TR,Runc,T{i},method,mode,X); - % - % [params_obj2{i}, hrf_obj2{i}, params_obj_dat2{i}, hrf_obj_dat2{i}] = hrf_fit(d{i},TR,Runc,T{i},method,mode,X_SPM); - % - % [params_obj3{i}, hrf_obj3{i}, params_obj_dat3{i}, hrf_obj_dat3{i}] = hrf_fit(d{i},TR,Runc,T{i},method,mode,X_SPM); - % - % [params_obj4{i}, hrf_obj4{i}, params_obj_dat4{i}, hrf_obj_dat4{i}] = hrf_fit(d{i},TR,Runc,T{i},method,1,X_SPM); - - info{i}.X=X; - info{i}.names=[SPM.Sess(i).U.name]'; - - % Testing - % Assuming i is defined, and your variables d, TR, Runc, T, method, mode, X, and X_SPM are initialized - - % Start or get the current parallel pool - % pool = gcp(); - % - % % Asynchronously call hrf_fit with the first set of arguments - % future1 = parfeval(pool, @hrf_fit, 4, d{i}, TR, Runc, T{i}, method, mode, X); - % - % % Asynchronously call hrf_fit with the second set of arguments - % future2 = parfeval(pool, @hrf_fit, 4, d{i}, TR, Runc, T{i}, method, mode, X_SPM); - % - % % Wait for the first job to finish and collect its results - % [params_obj1{i}, hrf_obj1{i}, params_obj_dat1{i}, hrf_obj_dat1{i}] = fetchOutputs(future1); - % - % % Wait for the second job to finish and collect its results - % [params_obj2{i}, hrf_obj2{i}, params_obj_dat2{i}, hrf_obj_dat2{i}] = fetchOutputs(future2); - - end - % Possibly need to drop null regressors? No - - - % betas = filenames(fullfile(pwd, 'data', 'sub-SID000743', '*heat_start*', '*ses-12*bodymap_run*.nii')); - % apply_nps([betas(3), betas(4), betas(2), betas(1)]) - % - % % Bug testing - % nps_test_cHRF={apply_nps(hrf_obj_dat3{1}), apply_nps(hrf_obj_dat3{2}), apply_nps(hrf_obj_dat3{3}), apply_nps(hrf_obj_dat3{4})} - % nps_test_FIR={apply_nps(hrf_obj_dat1{1}), apply_nps(hrf_obj_dat1{2}), apply_nps(hrf_obj_dat1{3}), apply_nps(hrf_obj_dat1{4})} - % nps_test_sFIR={apply_nps(hrf_obj_dat2{1}), apply_nps(hrf_obj_dat2{2}), apply_nps(hrf_obj_dat2{3}), apply_nps(hrf_obj_dat2{4})} - % - % figure - % plot([nps_test_FIR{1}{4}], 'r--'), hold on - % plot([nps_test_FIR{2}{4}], 'g--'), hold on - % plot([nps_test_FIR{3}{4}], 'b--'), hold on - % plot([nps_test_FIR{4}{1}], 'm--'), hold on - % - % plot([nps_test_FIR{1}{4}, nps_test_FIR{2}{4},nps_test_FIR{3}{4},nps_test_FIR{4}{1}]), hold on - % - % plot([nps_test_sFIR{1}{4}], 'r-'), hold on - % plot([nps_test_sFIR{2}{4}], 'g-'), hold on - % plot([nps_test_sFIR{3}{4}], 'b-'), hold on - % plot([nps_test_sFIR{4}{1}], 'm-'), hold on - % - % plot([nps_test_sFIR{1}{4}, nps_test_sFIR{2}{4},nps_test_sFIR{3}{4},nps_test_sFIR{4}{1}]), hold on - % - % hline(0,'k-') - % legend({'run1', 'run2', 'run3'}) - % legend({'run1', 'run2', 'run3', 'run4'}) - % - % % compare with betas - % - % % Check out what d looks like - % nps_d=apply_nps(d) - % plot([nps_d{:}]) - % - % % Checkout what spmify did - % figure - % nps_gkwyd=apply_nps(gkwy_data) - % plot([nps_gkwyd{:}]) - % - % % Plot to compare - % plot([nps_d{:}]), hold on - % plot([nps_gkwyd{:}]), hold on - % - % plot([nps_d{1}]), hold on - % plot([nps_gkwyd{1}]), hold on - % legend({'pre', 'post'}) - % - % plot([nps_d{2}]), hold on - % plot([nps_gkwyd{2}]), hold on - % legend({'pre', 'post'}) - % - % plot([nps_d{3}]), hold on - % plot([nps_gkwyd{3}]), hold on - % legend({'pre', 'post'}) - % - % plot([nps_d{4}]), hold on - % plot([nps_gkwyd{4}]), hold on - % legend({'pre', 'post'}) - % - - - - - - - -end - -end - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% -% Subfunctions -% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -function [h, dh, dh2] = CanonicalBasisSet(TR) - -len = round(30/TR); -xBF.dt = TR; -xBF.length= len; -xBF.name = 'hrf (with time and dispersion derivatives)'; -xBF = spm_get_bf(xBF); - -v1 = xBF.bf(1:len,1); -v2 = xBF.bf(1:len,2); -v3 = xBF.bf(1:len,3); - -h = v1; -dh = v2 - (v2'*v1/norm(v1)^2).*v1; -dh2 = v3 - (v3'*v1/norm(v1)^2).*v1 - (v3'*dh/norm(dh)^2).*dh; - -h = h./max(h); -dh = dh./max(dh); -dh2 = dh2./max(dh2); - -end \ No newline at end of file diff --git a/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/makeHRFstruct.m b/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/makeHRFstruct.m deleted file mode 100644 index b3193530..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/makeHRFstruct.m +++ /dev/null @@ -1,81 +0,0 @@ -function HRF_struct = makeHRFstruct(HRF_OBJ, CondNames, varargin) - - isSigFound = false; - isAtlasFound = false; - isRegsFound = false; - r = []; - s = []; - - if isa(HRF_OBJ, 'fmri_data') - HRF_OBJ={HRF_OBJ}; - end - - % Initialize the parallel pool if it's not already running - % if isempty(gcp('nocreate')) - % parpool; - % end - - for k = 1:length(varargin) - if strcmpi(varargin{k}, 'atlas') - if isa(varargin{k+1}, 'atlas') - at=varargin{k+1}; - else - error('Passed in atlas not an atlas.'); - end - - isAtlasFound = true; - end - - if strcmpi(varargin{k}, 'regions') - if isAtlasFound - if ischar(varargin{k+1}) - r={varargin{k+1}}; - isRegsFound = true; - elseif iscell(varargin{k+1}) - r=varargin{k+1}; - isRegsFound = true; - end - else - error('Cannot extract HRF of regions without atlas') - end - end - - if isAtlasFound && ~isRegsFound - % Extract all atlas regions. - r=at.labels; - end - - if strcmpi(varargin{k}, 'sig') % Expected input: 'nps', 'siips', or 'all' - if ischar(varargin{k+1}) - s={varargin{k+1}}; - isSigFound = true; - elseif iscell(varargin{k+1}) - s=varargin{k+1}; - isSigFound = true; - else - error(['Input argument for sig is unknown.']); - end - - end - end - - % ROIs will consist of regions and signatures - rois = [r, s]; - - CondNames=matlab.lang.makeValidName(CondNames); - - - - HRF=extractHRF(HRF_OBJ, CondNames, varargin{:}); - - HRF_struct=struct; - - if isAtlasFound - HRF_struct.atlas=at; - end - - HRF_struct.region=rois; - HRF_struct.CondNames=CondNames; - HRF_struct.fit=HRF; - -end \ No newline at end of file diff --git a/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/plotHRF.m b/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/plotHRF.m deleted file mode 100644 index 896fbc4d..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/plotHRF.m +++ /dev/null @@ -1,495 +0,0 @@ -function [model, models]=plotHRF(HRF, varargin) - % Generates a plot from an HRF Structure given a specified fit type and a region name. - % Michael Sun, Ph.D. - % - Takes HRF Structure object generated from EstimateHRF_inAtlas() - % - d is for selecting which data to plot, default is to aggregate them - % all. Putting a vector plots each file separately in order e.g., [1 2 3] - % - 'fit', t is a cellstring for fit type e.g., 'FIR', 'sFIR', 'IL', 'CHRF0', 'CHRF1', 'CHRF2' - % - 'conditions', c is a cellstring for condition names or stems to plot e.g., {'*hot*, *warm*'} - % - 'regions', r is a cellstring for region label from atlas.labels. e.g., 'ACC', or a cell-array of regions to compare relative to each other e.g., {'ACC', 'DLPFC'} - % - % *Usage: - % :: - % plotHRF(HRF_structure, 'FIR', 'CA2_Hippocampus_') - - % Check if the specified region 'r' and type 't' exist in HRF - % Requires the MATlab Signal Processing Toolbox to run - % detectPeaksandTroughs() - - % Flags to keep track of whether a cell array or atlas object is found - % isCellArrayFound = false; - - isAtlasFound = false; - isRegsFound = false; - isCondFound = false; - isFitFound = false; - - r=[]; - - - - % Get the list of conditions from HRF_PARAMS - conds = HRF.CondNames; - - for k = 1:length(varargin) - if strcmpi(varargin{k}, 'atlas') - if isa(varargin{k+1}, 'atlas') - at=varargin{k+1}; - else - error('Passed in atlas not an atlas.'); - end - isAtlasFound = true; - end - - if strcmpi(varargin{k}, 'regions') - if ischar(varargin{k+1}) - r=varargin{k+1}; - % Find the indices of the specified region and type - reg = find(ismember(HRF.region, r)); - - if ~ismember(r, HRF.region) - error('Invalid region specified.'); - end - - elseif iscell(varargin{k+1}) - r=varargin{k+1}; - isRegsFound = true; - end - - end - - if strcmpi(varargin{k}, 'conditions') - if ischar(varargin{k+1}) - conds=varargin{k+1}; - - if ~ismember(conds, HRF.CondNames) - error('Invalid condition specified.'); - end - - elseif iscell(varargin{k+1}) - conds=varargin{k+1}; - isCondFound = true; - else - disp(['Input argument for condition unknown.']); - end - - end - - if strcmpi(varargin{k}, 'fit') - if ischar(varargin{k+1}) - t=varargin{k+1}; - typ = find(ismember(HRF.types, t)); - disp(['Plotting ', t]) - - if ~ismember(t, HRF.types) - error('Invalid fit-type specified.'); - else - isFitFound = true; - end - elseif iscell(varargin{k+1}) - %% FIX THIS PART - t=varargin{k+1}; - isFitFound = true; - else - disp(['Input argument for fit-type unknown.']); - end - - end - end - - if isAtlasFound == false - at=HRF.atlas; - end - - if isRegsFound == false - reg=1:numel(HRF.region); - end - - if isCondFound == false - c=HRF.CondNames; - end - - if isFitFound == false - % Default to the first fit-type; - typ=1; - end - - - % end - % - % if isempty(r) && ~isCellArrayFound - % r=HRF.region; - % end - - - - % disp(reg) - % disp(typ) - - - % % Initialize a cell array to store the model matrices - % array3D = cell(1, length(conds)); - % - % % Populate the cell array with model matrices for each condition - % for c = 1:numel(conds) - % for i = 1:numel(r) - % if isfield(HRF.fit{typ}{i}, conds{f}) && isfield(HRF.fit{typ}{i}.(conds{f}), 'model') - % array3D{c}{i} = HRF.fit{typ}{i}.(conds{f}).model; - % else - % error('Model data not found for condition: %s', conds{f}); - % end - % end - % end - % - % % Check if any model matrices were found - % if all(cellfun(@isempty, array3D)) - % error('No model data found for the specified type and region.'); - % end - % - % % Concatenate the model matrices along the third dimension - % array3D = cat(3, array3D{:}); - - % Initialize color map and legend entries - % colors = jet(numel(conds)); - - colors = cbrewer2('qual', 'Accent', numel(reg)+1); - if numel(reg)>=4 - colors(4,:)=[]; % Remove yellow, too hard to see - end - model={}; - models={}; - - % Create a figure - figure; - - % Loop through each condition and plot - for cond = 1:numel(conds) - subplot(numel(conds), 1, cond); - - fieldname=fieldnames(HRF.fit{typ}{1}); - fld=char(fieldname(find(contains(fieldname, conds{cond})))); - - if numel(reg)==1 - model=HRF.fit{typ}{reg}.(conds{cond}).model; - - - % plot(HRF.fit{typ}{reg}.(conds{cond}).model, '-') - - % detectPeaksTroughs(squeeze(array3D(:, :, cond))', true); - detectPeaksTroughs(HRF.fit{typ}{reg}.(conds{cond}).model', true); - hold on; - - % Plot Standard Error if possible - try - se=HRF.fit{typ}{reg}.(conds{cond}).wse; - % Plot error shade: - x = 1:length(model); - upper_bound = model + se; - lower_bound = model - se; - % Create x values for fill (concatenate forward and reverse x values) - x_fill = [x, fliplr(x)]; - % Create y values for fill (concatenate upper_bound and reverse of lower_bound) - y_fill = [upper_bound, fliplr(lower_bound)]; - fill_color = [0.7, 0.7, 0.7]; % Change to desired color - fill_alpha = 0.3; % Transparency, change to desired value - fill(x_fill, y_fill, fill_color, 'FaceAlpha', fill_alpha, 'EdgeColor', 'none'); - hold on; - catch - - end - - hline(0); - - - if isfield(HRF.fit{typ}{reg}, conds{cond}) && isfield(HRF.fit{typ}{reg}.(fld), 'models') - models=HRF.fit{typ}{reg}.(conds{cond}).models; - - % Plot all underlying sublines - % for m = 1:height(HRF.fit{typ}{reg}.(conds{cond}).models) - % - % plot(HRF.fit{typ}{reg}.(conds{cond}).models(m,:), '-', 'Color', [0.9,0.9,0.9,0.2], 'LineWidth', 0.2); - % hold on; - % end - - end - - region=format_strings_for_legend(r(1)); - region=region{1}; - title({['Condition ', conds{cond}, ' Fit-type: ', strjoin(HRF.types(typ)), ' Regions: ', strjoin(HRF.region(reg))], ['Error: ', 'within-subject SE']}, 'Interpreter', 'none'); - - - else - h=[]; % Linehandles for legend and labels - for i = reg - [~, regionVoxNum, ~, ~]=at.select_atlas_subset(reg(i), 'exact').get_region_volumes; - if isfield(HRF.fit{typ}{i}, fld) && isfield(HRF.fit{typ}{i}.(fld), 'models') - models{i}=HRF.fit{typ}{i}.(fld).models/regionVoxNum; - end - - - - model{i}=HRF.fit{typ}{reg(i)}.(fld).model/regionVoxNum; - - % Plot Standard Error if possible - try - se{i}=HRF.fit{typ}{reg(i)}.(fld).wse/regionVoxNum; - - % Plot error shade: - x = 1:length(model{i}); - upper_bound = model{i} + se{i}; - lower_bound = model{i} - se{i}; - % Create x values for fill (concatenate forward and reverse x values) - x_fill = [x, fliplr(x)]; - % Create y values for fill (concatenate upper_bound and reverse of lower_bound) - y_fill = [upper_bound, fliplr(lower_bound)]; - fill_color = colors(i,:); % Change to desired color - fill_alpha = 0.3; % Transparency, change to desired value - fill(x_fill, y_fill, fill_color, 'FaceAlpha', fill_alpha, 'EdgeColor', 'none'); - hold on; - catch - - end - - h(i)=plot(HRF.fit{typ}{reg(i)}.(fld).model/regionVoxNum, '-', 'Color', colors(i,:), 'DisplayName', char(format_strings_for_legend(HRF.region(i)))); - hline(0); - title({['Condition ', conds{cond}, ' Fit-type: ', strjoin(HRF.types(typ)), ' Regions: ', strjoin(HRF.region(reg))], ['Error: ', 'within-subject SE']}, 'Interpreter', 'none'); - - label(h(end), format_strings_for_legend(HRF.region(i)), 'location', 'left', 'FontWeight', 'bold', 'Margin', 3, 'HorizontalAlignment', 'right', 'FontSize', 14); - label(h(end), format_strings_for_legend(HRF.region(i)), 'location', 'right', 'FontWeight', 'bold', 'Margin', 3, 'HorizontalAlignment', 'right', 'FontSize', 14); - label(h(end), format_strings_for_legend(HRF.region(i)), 'location', 'center', 'FontWeight', 'bold', 'Margin', 3, 'HorizontalAlignment', 'right', 'FontSize', 14); - label(h(end), format_strings_for_legend(HRF.region(i)), 'location', 'top', 'FontWeight', 'bold', 'Margin', 3, 'HorizontalAlignment', 'right', 'FontSize', 14); - - hold on; - end - % region_labels = format_strings_for_legend(r); - % legend(region_labels); - legend(h, format_strings_for_legend(HRF.region(reg))); - end - end - - -end - - - - - - -function plotRegionalHrfSummaries(HRF, regs) - % data: Cell array with the data to be processed - % conditions: Cell array with condition names - % signalNames: Cell array with signal names - % specificRegions: Cell array with specific regions for each signal and condition - - % Get specific regions for this signal and condition - regs = getSpecificRegions(specificRegions, i, c); - - regs=HRF.region; - - % regs={'ACC'}; - % Generate a set of maximally different colors - colormap('jet'); % Set colormap to parula - colors = colormap; % Get the colormap matrix - % - % % Select maximally different colors - nColors = numel(regs); - indices = round(linspace(1, size(colors, 1), nColors)); - maxDifferentColors = colors(indices, :); - - % plot time-to-peak, height, width, start_time, and end_time of every - % peaks_voxnormed and trough_voxnormed for a region - - create_figure(['Regional HRF summaries for ', HRF.CondNames{c}]); - for c = 1:numel(HRF.CondNames) - - - handles = []; - subplot(2, 2, c) - - for r=1:numel(regs) - - - % if c==3 - % - % % mean time-to-peak - % meant=[mean(dat{i}(dat{i}.condition==[conds{c},'-cue'] & dat{i}.region==regs{r}, :).t), ... - % mean(dat{i}(dat{i}.condition==conds{c} & dat{i}.region==regs{r}, :).t)+20]; - % - % % standard error time-to-peak - % stet=[std(dat{i}(dat{i}.condition==[conds{c}, '-cue'] & dat{i}.region==regs{r}, :).t)/sqrt(numel(dat{i}(dat{i}.condition==conds{c} & dat{i}.region==regs{r}, :).t)), ... - % std(dat{i}(dat{i}.condition==conds{c} & dat{i}.region==regs{r}, :).t)/sqrt(numel(dat{i}(dat{i}.condition==conds{c} & dat{i}.region==regs{r}, :).t))]; - % - % % mean height - % meanh=[mean(dat{i}(dat{i}.condition==[conds{c},'-cue'] & dat{i}.region==regs{r}, :).h), ... - % mean(dat{i}(dat{i}.condition==conds{c} & dat{i}.region==regs{r}, :).h)]; - % % height standard error - % steh=[std(dat{i}(dat{i}.condition==[conds{c},'-cue'] & dat{i}.region==regs{r}, :).h)/sqrt(numel(dat{i}(dat{i}.condition==[conds{c},'-cue'] & dat{i}.region==regs{r}, :).h)), ... - % std(dat{i}(dat{i}.condition==conds{c} & dat{i}.region==regs{r}, :).h)/sqrt(numel(dat{i}(dat{i}.condition==conds{c} & dat{i}.region==regs{r}, :).h))]; - % - % % minimum width - % minw=meant-[mean(dat{i}(dat{i}.condition==[conds{c},'-cue'] & dat{i}.region==regs{r}, :).w_times(:,1)), mean(dat{i}(dat{i}.condition==conds{c} & dat{i}.region==regs{r}, :).w_times(:,1))+20]; - % % maximum width - % maxw=[mean(dat{i}(dat{i}.condition==[conds{c},'-cue'] & dat{i}.region==regs{r}, :).w_times(:,2)), mean(dat{i}(dat{i}.condition==conds{c} & dat{i}.region==regs{r}, :).w_times(:,2))+20]-meant; - % - % handles(end+1)=errorbar(meant, meanh, steh, steh, stet, stet, 'o-', 'Color', maxDifferentColors(r,:)); - % - % % Calculate the position and size of the rectangle - % rectX = meant(1) - minw(1); % left boundary of the rectangle - % rectY = meanh(1) - max(steh(1)); % bottom boundary of the rectangle - % rectWidth = (meant(1)+maxw(1))-(meant(1)-minw(1)); % width of the rectangle - % rectHeight = 2*steh(1); % height of the rectangle - % - % % Draw the rectangle - % % rectangle('Position', [rectX, rectY, rectWidth, rectHeight], 'FaceColor', colors(r,:), 'LineWidth', 1); - % patch([rectX, rectX+rectWidth, rectX+rectWidth, rectX], [rectY, rectY, rectY+rectHeight, rectY+rectHeight], maxDifferentColors(r,:), 'FaceAlpha', 0.2); - % - % % Calculate the position and size of the rectangle - % rectX = meant(2) - minw(2); % left boundary of the rectangle - % rectY = meanh(2) - max(steh(2)); % bottom boundary of the rectangle - % rectWidth = (meant(2)+maxw(2))-(meant(2)-minw(2)); % width of the rectangle - % rectHeight = 2*steh(2); % height of the rectangle - % - % % Draw the rectangle - % % rectangle('Position', [rectX, rectY, rectWidth, rectHeight], 'EdgeColor', 'r', 'LineWidth', 1); - % patch([rectX, rectX+rectWidth, rectX+rectWidth, rectX], [rectY, rectY, rectY+rectHeight, rectY+rectHeight], maxDifferentColors(r,:), 'FaceAlpha', 0.2); - % - % label(handles(end), format_strings_for_legend(regs{r}), 'location', 'right', 'FontWeight', 'bold', 'Margin', 3, 'HorizontalAlignment', 'right', 'FontSize', 14); - % label(handles(end), format_strings_for_legend(regs{r}), 'location', 'left', 'FontWeight', 'bold', 'Margin', 3, 'HorizontalAlignment', 'right', 'FontSize', 14); - % % label(handles(end), format_strings_for_legend(regs{r}), 'location', 'right', 'slope', 'FontWeight', 'bold', 'Margin', 3, 'HorizontalAlignment', 'right', 'FontSize', 14); - % % label(handles(end), format_strings_for_legend(regs{r}), 'location', 'top', 'slope', 'FontWeight', 'bold', 'Margin', 3, 'HorizontalAlignment', 'right', 'FontSize', 14); - % % label(handles(end), format_strings_for_legend(regs{r}), 'location', 'bottom', 'slope', 'FontWeight', 'bold', 'Margin', 3, 'HorizontalAlignment', 'right', 'FontSize', 14); - % - % - % else - % mean time-to-peak - - meant=[]; - meanh=[]; - minw=[]; - maxw=[]; - - for p=1:numel(HRF.fit{1}{r}.(HRF.CondNames{c}).peaks_voxnormed) - - % meant=mean(dat{i}(dat{i}.condition==conds{c} & dat{i}.region==regs{r}, :).t); - meant=[meant, HRF.fit{1}{r}.(HRF.CondNames{c}).peaks_voxnormed(p).time_to_peak]; - - % standard error time-to-peak - % stet=std(dat{i}(dat{i}.condition==conds{c} & dat{i}.region==regs{r}, :).t)/sqrt(numel(dat{i}(dat{i}.condition==conds{c} & dat{i}.region==regs{r}, :).t)); - - % mean height - % meanh=mean(dat{i}(dat{i}.condition==conds{c} & dat{i}.region==regs{r}, :).h); - meanh=[meanh, HRF.fit{1}{r}.(HRF.CondNames{c}).peaks_voxnormed(p).height]; - - % height standard error - % steh=std(dat{i}(dat{i}.condition==conds{c} & dat{i}.region==regs{r}, :).h)/sqrt(numel(dat{i}(dat{i}.condition==conds{c} & dat{i}.region==regs{r}, :).h)); - - % minimum width - % minw=meant-mean(dat{i}(dat{i}.condition==conds{c} & dat{i}.region==regs{r}, :).w_times(:,1)); - % maximum width - % maxw=mean(dat{i}(dat{i}.condition==conds{c} & dat{i}.region==regs{r}, :).w_times(:,2))-meant; - - minw=[minw, HRF.fit{1}{r}.(HRF.CondNames{c}).peaks_voxnormed(p).start_time]; - maxw=[maxw, HRF.fit{1}{r}.(HRF.CondNames{c}).peaks_voxnormed(p).end_time]; - - end - - for t=1:numel(HRF.fit{1}{r}.(HRF.CondNames{c}).troughs_voxnormed) - - % meant=mean(dat{i}(dat{i}.condition==conds{c} & dat{i}.region==regs{r}, :).t); - meant=[meant, HRF.fit{1}{r}.(HRF.CondNames{c}).troughs_voxnormed(t).time_to_peak]; - - % standard error time-to-peak - % stet=std(dat{i}(dat{i}.condition==conds{c} & dat{i}.region==regs{r}, :).t)/sqrt(numel(dat{i}(dat{i}.condition==conds{c} & dat{i}.region==regs{r}, :).t)); - - % mean height - % meanh=mean(dat{i}(dat{i}.condition==conds{c} & dat{i}.region==regs{r}, :).h); - meanh=[meanh, HRF.fit{1}{r}.(HRF.CondNames{c}).troughs_voxnormed(t).height]; - - % height standard error - % steh=std(dat{i}(dat{i}.condition==conds{c} & dat{i}.region==regs{r}, :).h)/sqrt(numel(dat{i}(dat{i}.condition==conds{c} & dat{i}.region==regs{r}, :).h)); - - % minimum width - % minw=meant-mean(dat{i}(dat{i}.condition==conds{c} & dat{i}.region==regs{r}, :).w_times(:,1)); - % maximum width - % maxw=mean(dat{i}(dat{i}.condition==conds{c} & dat{i}.region==regs{r}, :).w_times(:,2))-meant; - - minw=[minw, HRF.fit{1}{r}.(HRF.CondNames{c}).troughs_voxnormed(t).start_time]; - maxw=[maxw, HRF.fit{1}{r}.(HRF.CondNames{c}).troughs_voxnormed(t).end_time]; - - end - - % handles(end+1)=errorbar(meant, meanh, steh, steh, stet, stet, 'o', 'Color', maxDifferentColors(r,:)); - [~,vox,~,~]=get_region_volumes(at); - vox=vox(r); - se=barplot_get_within_ste(HRF.fit{1}{r}.(HRF.CondNames{c}).models); - se=se/vox; - - se=repmat(se, 1, numel(meant)); - - handles=[handles, errorbar(meant, meanh, se, se, repmat(0, 1, numel(meant)), repmat(0, 1, numel(meant)), 'o-', 'Color', maxDifferentColors(r,:))]; - - if numel(meant) > 1 - label(handles(end), format_strings_for_legend(regs{r}), 'location', 'left', 'FontWeight', 'bold', 'Margin', 3, 'HorizontalAlignment', 'right', 'FontSize', 14); - label(handles(end), format_strings_for_legend(regs{r}), 'location', 'right', 'FontWeight', 'bold', 'Margin', 3, 'HorizontalAlignment', 'right', 'FontSize', 14); - else - label(handles(end), format_strings_for_legend(regs{r}), 'location', 'right', 'FontWeight', 'bold', 'Margin', 3, 'HorizontalAlignment', 'right', 'FontSize', 14); - end - - % % Calculate the position and size of the rectangle - % rectX = meant - minw; % left boundary of the rectangle - % % rectY = meanh - max(steh); % bottom boundary of the rectangle - % rectY = meanh - meanh/10; % placeholder for now - % rectWidth = (meant+maxw)-(meant-minw); % width of the rectangle - % % rectHeight = 2*steh; % height of the rectangle - % rectHeight = 2*(meanh/10); % placeholder for now - % - % patch([rectX, rectX+rectWidth, rectX+rectWidth, rectX], [rectY, rectY, rectY+rectHeight, rectY+rectHeight], maxDifferentColors(r,:), 'FaceAlpha', 0.2); - - - % end - - hold on - end - - hold off - - xlim([0,45]); - - hlin=refline(0,0); - hlin.Color='k'; - - vline(13/0.46, 'r--'); - - legend(handles, format_strings_for_legend(regs), 'Location', 'best'); - ylabel('Similarity Amplitude') - xticks([0:5:45]) - xticklabels([0:5:45]*0.46) - xlabel('Time to Peak (seconds)'); - % title({[signames{i}, ' Condition: ', conds{c}], 'HRF Summary Statistics', 'With Standard Errors, Stimulus offset is demarcated'}) - title({['Condition: ', HRF.CondNames{c}], 'HRF Summary Statistics', 'With Standard Errors, Stimulus offset is demarcated'}) - - end -end - -function regs = getSpecificRegions(specificRegions, signalIdx, conditionIdx) - % Extract and/or calculate the specific regions for the given signal and condition. - % specificRegions: Cell array defining specific regions for each signal and condition - % signalIdx: Index of the current signal - % conditionIdx: Index of the current condition - - % Example logic - adapt as per your actual requirements - regs = specificRegions{signalIdx, conditionIdx}; -end - - -% -% barplot_columns() -% -% model3d=cat(3, WASABIROIs_HRF_3.fit{1}{1}.hot.models, WASABIROIs_HRF_3.fit{1}{1}.warm.models, WASABIROIs_HRF_3.fit{1}{1}.imagine.models) -% -% [se_within, stats]=barplot_get_within_ste(WASABIROIs_HRF_3.fit{1}{1}.hot.models) - - - - - diff --git a/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/plotRegionalHrfSummaries.m b/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/plotRegionalHrfSummaries.m deleted file mode 100644 index f48fa883..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/plotRegionalHrfSummaries.m +++ /dev/null @@ -1,201 +0,0 @@ -function plotRegionalHrfSummaries(HRF, at, varargin) - % data: Cell array with the data to be processed - % conditions: Cell array with condition names - % signalNames: Cell array with signal names - % specificRegions: Cell array with specific regions for each signal and condition - - % Get specific regions for this signal and condition - % regs = getSpecificRegions(specificRegions, i, c); - - % regs = getSpecificRegions(regs, i, c); - - if numel(varargin)>0 - if iscell(varargin{1}) - regs=varargin{1}; - else - regs=HRF.region; - end - else - regs=HRF.region; - end - - % regs={'ACC'}; - % Generate a set of maximally different colors - % colormap('jet'); % Set colormap to parula - % colors = colormap; % Get the colormap matrix - - % Get the colors - colors = cbrewer2('qual', 'Accent', numel(regs)+3); - - % Define a threshold to identify yellow colors (you may need to adjust this) - yellowThreshold = 0.8; - - % Identify the rows in 'colors' that correspond to yellow shades - yellowRows = all(colors(:,1:2) > yellowThreshold, 2); - - % Remove the yellow colors - colors(yellowRows, :) = []; - - % - % % Select maximally different colors - nColors = numel(regs); - indices = round(linspace(1, size(colors, 1), nColors)); - maxDifferentColors = colors(indices, :); - - % plot time-to-peak, height, width, start_time, and end_time of every - % peaks_voxnormed and trough_voxnormed for a region - - % create_figure(['Regional HRF summaries for ', HRF.params.CondNames{c}]); - figure; - for c = 1:numel(HRF.params.CondNames) - - - handles = []; - subplot(2, 2, c) - - for r=1:numel(regs) - - meant=[]; - meanh=[]; - minw=[]; - maxw=[]; - - for p=1:numel(HRF.fit{1}{r}.(HRF.params.CondNames{c}).peaks_voxnormed) - - % meant=mean(dat{i}(dat{i}.condition==conds{c} & dat{i}.region==regs{r}, :).t); - meant=[meant, HRF.fit{1}{r}.(HRF.params.CondNames{c}).peaks_voxnormed(p).time_to_peak]; - - % standard error time-to-peak - % stet=std(dat{i}(dat{i}.condition==conds{c} & dat{i}.region==regs{r}, :).t)/sqrt(numel(dat{i}(dat{i}.condition==conds{c} & dat{i}.region==regs{r}, :).t)); - - % mean height - % meanh=mean(dat{i}(dat{i}.condition==conds{c} & dat{i}.region==regs{r}, :).h); - meanh=[meanh, HRF.fit{1}{r}.(HRF.params.CondNames{c}).peaks_voxnormed(p).height]; - - % height standard error - % steh=std(dat{i}(dat{i}.condition==conds{c} & dat{i}.region==regs{r}, :).h)/sqrt(numel(dat{i}(dat{i}.condition==conds{c} & dat{i}.region==regs{r}, :).h)); - - % minimum width - % minw=meant-mean(dat{i}(dat{i}.condition==conds{c} & dat{i}.region==regs{r}, :).w_times(:,1)); - % maximum width - % maxw=mean(dat{i}(dat{i}.condition==conds{c} & dat{i}.region==regs{r}, :).w_times(:,2))-meant; - - minw=[minw, HRF.fit{1}{r}.(HRF.params.CondNames{c}).peaks_voxnormed(p).start_time]; - maxw=[maxw, HRF.fit{1}{r}.(HRF.params.CondNames{c}).peaks_voxnormed(p).end_time]; - - end - - for t=1:numel(HRF.fit{1}{r}.(HRF.params.CondNames{c}).troughs_voxnormed) - - % meant=mean(dat{i}(dat{i}.condition==conds{c} & dat{i}.region==regs{r}, :).t); - meant=[meant, HRF.fit{1}{r}.(HRF.params.CondNames{c}).troughs_voxnormed(t).time_to_peak]; - - % standard error time-to-peak - % stet=std(dat{i}(dat{i}.condition==conds{c} & dat{i}.region==regs{r}, :).t)/sqrt(numel(dat{i}(dat{i}.condition==conds{c} & dat{i}.region==regs{r}, :).t)); - - % mean height - % meanh=mean(dat{i}(dat{i}.condition==conds{c} & dat{i}.region==regs{r}, :).h); - meanh=[meanh, HRF.fit{1}{r}.(HRF.params.CondNames{c}).troughs_voxnormed(t).height]; - - % height standard error - % steh=std(dat{i}(dat{i}.condition==conds{c} & dat{i}.region==regs{r}, :).h)/sqrt(numel(dat{i}(dat{i}.condition==conds{c} & dat{i}.region==regs{r}, :).h)); - - % minimum width - % minw=meant-mean(dat{i}(dat{i}.condition==conds{c} & dat{i}.region==regs{r}, :).w_times(:,1)); - % maximum width - % maxw=mean(dat{i}(dat{i}.condition==conds{c} & dat{i}.region==regs{r}, :).w_times(:,2))-meant; - - minw=[minw, HRF.fit{1}{r}.(HRF.params.CondNames{c}).troughs_voxnormed(t).start_time]; - maxw=[maxw, HRF.fit{1}{r}.(HRF.params.CondNames{c}).troughs_voxnormed(t).end_time]; - - end - - % handles(end+1)=errorbar(meant, meanh, steh, steh, stet, stet, 'o', 'Color', maxDifferentColors(r,:)); - [~,vox,~,~]=at.select_atlas_subset(regs(r), 'exact').get_region_volumes; - % vox=vox(r); - se=barplot_get_within_ste(HRF.fit{1}{r}.(HRF.params.CondNames{c}).models); - se=se/vox; - - se=repmat(se, 1, numel(meant)); - - % Sort and plot - - % Combine the data into a single matrix for sorting - data = [meant', meanh', minw', maxw', se']; - - % Sort the data based on meant values (1st column) - sortedData = sortrows(data, 1); - - % Extract the sorted data - meantSorted = sortedData(:, 1)'; - meanhSorted = sortedData(:, 2)'; - minwSorted = sortedData(:, 3)'; - maxwSorted = sortedData(:, 4)'; - seSorted = sortedData(:, 5)'; - - % Plot the sorted data - % handles=[handles, errorbar(meant, meanh, se, se, repmat(0, 1, numel(meant)), repmat(0, 1, numel(meant)), 'o-', 'Color', maxDifferentColors(r,:))]; - handles = [handles, errorbar(meantSorted, meanhSorted, seSorted, seSorted, zeros(1, numel(meantSorted)), zeros(1, numel(meantSorted)), 'o-', 'Color', maxDifferentColors(r,:))]; - - if numel(meant) > 1 - label(handles(end), format_strings_for_legend(regs{r}), 'location', 'left', 'FontWeight', 'bold', 'Margin', 3, 'HorizontalAlignment', 'right', 'FontSize', 14); - label(handles(end), format_strings_for_legend(regs{r}), 'location', 'right', 'FontWeight', 'bold', 'Margin', 3, 'HorizontalAlignment', 'right', 'FontSize', 14); - else - label(handles(end), format_strings_for_legend(regs{r}), 'location', 'right', 'FontWeight', 'bold', 'Margin', 3, 'HorizontalAlignment', 'right', 'FontSize', 14); - end - - % % Calculate the position and size of the rectangle - % rectX = meant - minw; % left boundary of the rectangle - % % rectY = meanh - max(steh); % bottom boundary of the rectangle - % rectY = meanh - meanh/10; % placeholder for now - % rectWidth = (meant+maxw)-(meant-minw); % width of the rectangle - % % rectHeight = 2*steh; % height of the rectangle - % rectHeight = 2*(meanh/10); % placeholder for now - % - % patch([rectX, rectX+rectWidth, rectX+rectWidth, rectX], [rectY, rectY, rectY+rectHeight, rectY+rectHeight], maxDifferentColors(r,:), 'FaceAlpha', 0.2); - - % Loop through each rectangle and plot - for i = 1:numel(minwSorted) - rectangleHeight=seSorted(i)*2; - x = minwSorted(i); - y = meanhSorted(i) - rectangleHeight / 2; % Adjust y to center the rectangle on meanh - width = maxwSorted(i) - minwSorted(i); - height = rectangleHeight; - - % Plot the rectangle - rectangle('Position', [x, y, width, height], 'EdgeColor', maxDifferentColors(r,:)); - end - - hold on - - end - - % hold off - - xlim([0,45]); - - hlin=refline(0,0); - hlin.Color='k'; - - vline(13/0.46, 'r--'); - - legend(handles, format_strings_for_legend(regs), 'Location', 'best'); - ylabel('Similarity Amplitude') - xticks([0:5:45]) - xticklabels([0:5:45]*0.46) - xlabel('Time to Peak (seconds)'); - % title({[signames{i}, ' Condition: ', conds{c}], 'HRF Summary Statistics', 'With Standard Errors, Stimulus offset is demarcated'}) - title({['Condition: ', HRF.params.CondNames{c}], 'HRF Summary Statistics', 'With Standard Errors, Stimulus offset is demarcated'}) - - end -end - -function regs = getSpecificRegions(specificRegions, signalIdx, conditionIdx) - % Extract and/or calculate the specific regions for the given signal and condition. - % specificRegions: Cell array defining specific regions for each signal and condition - % signalIdx: Index of the current signal - % conditionIdx: Index of the current condition - - % Example logic - adapt as per your actual requirements - regs = specificRegions{signalIdx, conditionIdx}; -end diff --git a/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/process_HRF_dir.m b/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/process_HRF_dir.m deleted file mode 100644 index 23661360..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/EstHRF_inAtlas/process_HRF_dir.m +++ /dev/null @@ -1,64 +0,0 @@ -function [HRF_data_lvl2, HRF_data, tc_data]=process_HRF_dir(basedir, at) - % Helper script to process an estHRF directory for second-level - % analysis - % Michael Sun, Ph.D. - % - Takes a BIDS-formatted estHRF directory - % - basedir: charstr fullpath of the directory: e.g., '//dartfs-hpc/rc/lab/C/CANlab/labdata/data/WASABI/derivatives/estHRF_NPSpos' - % - at: atlas object - % *Usage: - % :: - % [HRF_data_lvl2, HRF_data, tc_data] = process_HRF_dir(basedir, atlas_obj}) - % for s = 1:numel(subjects) - % disp(subjects{s}); - % describeHRF(HRF_data{s}); - % T{s}=generateHRFTable(HRF_data{s}); - % plotHRF(HRF_data_lvl2{s}, 'FIR', my_atlas); - % end - % - - % Validate input arguments - if nargin < 2 - error('You must provide a base directory and atlas object.'); - end - - % Load up all the .mat files you generated with estHRF_inAtlas for each subject - subjects = canlab_list_subjects(basedir, 'sub-*'); - HRF_data = cell(1, length(subjects)); - HRF_data_lvl2 = cell(1, length(subjects)); - tc_data = cell(1, length(subjects)); - HRF_files = cell(1, length(subjects)); - - for s = 1:numel(subjects) - HRF_files{s} = dir(fullfile(basedir, subjects{s}, '**', '*.mat')); - - % Initialize cell arrays to store the data - HRF_data{s} = cell(1, length(HRF_files{s})); - - tc_data{s} = cell(1, length(HRF_files{s})); - - % Loop through each file and load the data - for k = 1:length(HRF_files{s}) - fullpath = fullfile(HRF_files{s}(k).folder, HRF_files{s}(k).name); - data = load(fullpath); % Load the .mat file - - % Assuming each .mat file contains variables named 'HRF' and 'tc' - HRF_data{s}{k} = data.HRF; - tc_data{s}{k} = data.tc; - - if numel(data.HRF)>1 - HRF_data_lvl2{s}{k}=HRF_avg(HRF_data{s}{k}, at, 'conditions', {'heat_start', 'warm_start', 'imagine_cue', 'imagine_start'}); - - end - - end - - - % What if each HRF_data is a cell array of sessions? - if isstruct(HRF_data{s}) - HRF_data_lvl2{s}=HRF_avg(HRF_data{s}, at, 'conditions', {'heat_start', 'warm_start', 'imagine_cue', 'imagine_start'}); - end - - end - - -end \ No newline at end of file diff --git a/CanlabCore/HRF_Est_Toolbox2/Example.m b/CanlabCore/HRF_Est_Toolbox2/Example.m deleted file mode 100644 index a508229d..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/Example.m +++ /dev/null @@ -1,298 +0,0 @@ -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% -% Example code for estimating the HRF using the Inverse-Logit Model, a -% Finte Impluse Response Model and the Canonical HRF with 2 derivatives. -% Also the code illustrates our code for detecting model misspecification. -% -% By Martin Lindquist and Tor Wager -% Created 10/02/09 -% Last edited 03/12/23 -% 03/12/23 - Added Logit model -% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% -% Load time course -% - -addpath('/Users/martinlindquist/Dropbox/fdaM/Matlabfunctions') - -mypath = which('ilogit'); -if isempty(mypath), error('Cannot find directory with ilogit.m and other functions. Not on path?'); end -[mydir] = fileparts(mypath) - -load(fullfile(mydir,'timecourse')) - -tc = (tc- mean(tc))/std(tc); -len = length(tc); - - -%% Or: create your own -[xBF] = spm_get_bf(struct('dt', .5, 'name', 'hrf (with time and dispersion derivatives)', 'length', 32)); -clear Xtrue -for i = 1:1, xx = conv(xBF.bf(:,i), [1 1 1 1 1 1 ]'); - Xtrue(:, i) = xx(1:66); -end -for i = 2:3, xx = conv(xBF.bf(:,i), [1 1]'); - Xtrue(:, i) = xx(1:66); -end -hrf = Xtrue * [1 .3 .2]'; -xsecs = 0:.5:32; - -hrf = [ 0; 0; hrf]; -hrf = hrf(1:length(xsecs)); -hrf = hrf ./ max(hrf); -figure; plot(xsecs, hrf, 'k') -%hrf = hrf(1:4:end); % downsample to TR, if TR is > 0.5 - -b = 1; -R = randperm(640); R = sort(R(1:36)); -Run = zeros(640,1); -for i=1:length(R), Run(R(i)) = 1; end -true_sig = b*conv(Run, hrf); -true_sig = true_sig(1:640); - -tc_noise = noise_arp(640, [.3 0]); -tc = true_sig + 0.5 * tc_noise; -% tc = true_sig; -%figure; plot(tc); - - -Runc{1} = Run; - -%% - -create_figure; subplot(3,1,1); han = plot(tc); -title('Sample time course'); drawnow - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% -% Settings -% - -TR = 0.5; -%T = round(30/TR); -T = 30; -t = 1:TR:T; % samples at which to get Logit HRF Estimate -FWHM = 4; % FWHM for residual scan -pval = 0.01; -df = 600; -alpha = 0.001; - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% -% Create stick function (sample event onsets) -% Variable R contains onset times -% Variable Run contains stick (a.k.a. delta or indicator) function - -% R = [3, 21, 56, 65, 109, 126, 163, 171, 216, 232, 269, 282, 323, 341, 376, 385, 429, 446, 483, 491, 536, 552, 589, 602]; -% Run = zeros(640,1); -% for i=1:length(R), Run(R(i)) = 1; end; -% - -try - hold on; - hh = plot_onsets(R,'k',-3,1, 1); - drawnow -catch - disp('Couldn''t find function to add onset sticks to plot. Skipping.') -end - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Fit HRF using IL-function - -% Choose mode (deterministic/stochastic) - -mode = 0; % 0 - deterministic aproach - % 1 - simulated annealing approach - % Please note that when using simulated annealing approach you - % may need to perform some tuning before use. - -[h1, fit1, e1, param] = Fit_Logit2(tc,TR,Runc,T,mode); -[pv sres sres_ns1] = ResidScan(e1, FWHM); -[PowLoss1] = PowerLoss(e1, fit1, (len-7) , tc, TR, Runc, alpha); - -hold on; han(2) = plot(fit1,'r'); - -disp('Summary: IL_function'); - -disp('Amplitude:'); disp(param(1)); -disp('Time-to-peak:'); disp(param(2)*TR); -disp('Width:'); disp(param(3)*TR); - -disp('MSE:'); disp((1/(len-1)*sum(e1.^2))); -disp('Mis-modeling:'); disp(pv); -disp('Power Loss:'); disp(PowLoss1); - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Fit HRF using FIR-model - -% Choose mode (FIR/sFIR) - -mode = 1; % 0 - FIR - % 1 - smooth FIR - -[h2, fit2, e2, param] = Fit_sFIR(tc,TR,Runc,T,mode); -[pv sres sres_ns2] = ResidScan(e2, FWHM); -[PowLoss2] = PowerLoss(e2, fit2, (len-T) , tc, TR, Runc, alpha); - -hold on; han(3) = plot(fit2,'g'); - -disp('Summary: FIR'); - -disp('Amplitude'); disp(param(1)); -disp('Time-to-peak'); disp(param(2)*TR); -disp('Width'); disp(param(3)*TR); - -disp('MSE:'); disp((1/(len-1)*sum(e2.^2))); -disp('Mis-modeling'); disp(pv); -disp('Power Loss:'); disp(PowLoss2); - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Fit HRF using Canonical HRF + 2 derivatives - -p=1; - -[h3, fit3, e3, param, info] = Fit_Canonical_HRF(tc,TR,Runc,30,p); -[pv sres sres_ns3] = ResidScan(e3, FWHM); -[PowLoss3] = PowerLoss(e3, fit3, (len-p) , tc, TR, Runc, alpha); - -hold on; han(4) = plot(fit3,'m'); - -legend(han,{'Data' 'IL' 'sFIR' 'DD'}) - - -disp('Summary: Canonical + 2 derivatives'); - -disp('Amplitude'); disp(param(1)); -disp('Time-to-peak'); disp(param(2)*TR); -disp('Width'); disp(param(3)*TR); - -disp('MSE:'); disp((1/(len-1)*sum(e3.^2))); -disp('Mis-modeling'); disp(pv); -disp('Power Loss:'); disp(PowLoss3); - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Fit HRF using B-splies - -[h4, fit4, e4, param] = Fit_Spline(tc, TR, Runc, 30); -[pv sres sres_ns4] = ResidScan(e4, FWHM); -[PowLoss4] = PowerLoss(e4, fit4, (len-p) , tc, TR, Runc, alpha); - -hold on; han(5) = plot(fit4,'m'); - -legend(han,{'Data' 'IL' 'sFIR' 'DD' 'Spline'}) - - -disp('Summary: B-spline'); - -disp('Amplitude'); disp(param(1)); -disp('Time-to-peak'); disp(param(2)*TR); -disp('Width'); disp(param(3)*TR); - -disp('MSE:'); disp((1/(len-1)*sum(e4.^2))); -disp('Mis-modeling'); disp(pv); -disp('Power Loss:'); disp(PowLoss4); - - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Fit HRF using non-linear gamma function - -[h5, fit5, e5, param] = Fit_NLgamma(tc, TR, Runc, 30); -[pv sres sres_ns5] = ResidScan(e5, FWHM); -[PowLoss5] = PowerLoss(e5, fit5, (len-p) , tc, TR, Runc, alpha); - -hold on; han(6) = plot(fit5,'y'); - -legend(han,{'Data' 'IL' 'sFIR' 'DD' 'Spline' 'NL'}) - - -disp('Summary: Non-linear gamma'); - -disp('Amplitude'); disp(param(1)); -disp('Time-to-peak'); disp(param(2)*TR); -disp('Width'); disp(param(3)*TR); - -disp('MSE:'); disp((1/(len-1)*sum(e5.^2))); -disp('Mis-modeling'); disp(pv); -disp('Power Loss:'); disp(PowLoss5); - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%figure; -%% - -subplot(3,2,5); hold on; -plot(xsecs, hrf, 'k') -xsecs1 = xsecs(1:length(h1)); -han2 = plot(xsecs1, h1,'r'); -xsecs2 = xsecs(1:length(h2)); -han2(2) = plot(xsecs2, h2,'g'); -xsecs3 = xsecs(1:length(h3)); -han2(3) = plot(xsecs3, h3,'m'); -xsecs4 = xsecs(1:length(h4)); -han2(4) = plot(xsecs4, h4,'b'); -xsecs5 = xsecs(1:length(h5)); -han2(5) = plot(xsecs5, h5,'y'); -legend(han2,{'IL' 'sFIR' 'DD' 'Spline' 'NL'}) -title('Estimated HRF'); - - -subplot(3,1,2); hold on; -hh = plot_onsets(R,'k',-3,1); -drawnow - -han3 = plot(sres_ns1,'r'); -hold on; han3(2) = plot(sres_ns2,'g'); -hold on; han3(3) = plot(sres_ns3,'m'); -hold on; han3(4) = plot(sres_ns4,'b'); -hold on; han3(5) = plot(sres_ns5,'y'); -hold on; plot((1:len),zeros(len,1),'--k'); -legend(han3,{'IL' 'sFIR' 'DD' 'Spline' 'NL'}) -title('Mis-modeling (time course)'); - - -subplot(3,2,6); hold on; - -[s1] = Fit_sFIR(sres_ns1,TR,Runc,T,0); -[s2] = Fit_sFIR(sres_ns2,TR,Runc,T,0); -[s3] = Fit_sFIR(sres_ns3,TR,Runc,T,0); -[s4] = Fit_sFIR(sres_ns4,TR,Runc,T,0); -[s5] = Fit_sFIR(sres_ns5,TR,Runc,T,0); - -han4 = plot(s1(1:T),'r'); -hold on; han4(2) = plot(s2(1:T),'g'); -hold on; han4(3) = plot(s3(1:T),'m'); -hold on; han4(4) = plot(s4(1:T),'b'); -hold on; han4(5) = plot(s5(1:T),'y'); -hold on; plot((1:T),zeros(T,1),'--k'); -legend(han4,{'IL' 'sFIR' 'DD' 'Spline' 'NL'}) -title('Mis-modeling (HRF)'); - - -%% - - -figure; hold on; - -plot(xsecs, b*hrf, '--k', 'LineWidth', 2) - -xsecs1 = xsecs(1:length(h1)); -han2 = plot(xsecs1, h1,'r', 'LineWidth', 2); -xsecs2 = xsecs(1:length(h2)); -han2(2) = plot(xsecs2, h2,'g', 'LineWidth', 2); -xsecs3 = xsecs(1:length(h3)); -han2(3) = plot(xsecs3, h3,'m', 'LineWidth', 2); -xsecs4 = xsecs(1:length(h4)); -han2(4) = plot(xsecs4, h4,'b', 'LineWidth', 2); -xsecs5 = xsecs(1:length(h5)); -han2(5) = plot(xsecs5, h5,'y', 'LineWidth', 2); - -legend(han2,{'IL' 'sFIR' 'DD' 'Spline' 'NL'}) -title('Estimated HRF'); - diff --git a/CanlabCore/HRF_Est_Toolbox2/Example2.m b/CanlabCore/HRF_Est_Toolbox2/Example2.m deleted file mode 100644 index 2d018f31..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/Example2.m +++ /dev/null @@ -1,369 +0,0 @@ -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% -% Example code for estimating the HRF using the Inverse-Logit Model, a -% Finte Impluse Response Model and the Canonical HRF with 2 derivatives. -% Also the code illustrates our code for detecting model misspecification. -% -% By Martin Lindquist and Tor Wager -% Created 03/09/23 -% Last edited 03/09/23 -% Added multi-simulus simulation to Example.m -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% -% Create time course -% - -mypath = which('ilogit'); -if isempty(mypath), error('Cannot find directory with ilogit.m and other functions. Not on path?'); end -[mydir] = fileparts(mypath); - -load(fullfile(mydir,'timecourse')) - -tc = (tc- mean(tc))/std(tc); -len = length(tc); - - -%% -% Create HRFs - -[xBF] = spm_get_bf(struct('dt', .5, 'name', 'hrf (with time and dispersion derivatives)', 'length', 32)); -clear Xtrue1 -for i = 1:1, xx = conv(xBF.bf(:,i), [1 1 1 1 1 1 ]'); - Xtrue1(:, i) = xx(1:66); -end -for i = 2:3, xx = conv(xBF.bf(:,i), [1 1]'); - Xtrue1(:, i) = xx(1:66); -end -hrf1 = Xtrue1 * [1 .5 .3]'; - - -clear Xtrue2 -for i = 1:1, xx = conv(xBF.bf(:,i), [1 1 ]'); - Xtrue2(:, i) = xx(1:66); -end -for i = 2:3, xx = conv(xBF.bf(:,i), [1 1]'); - Xtrue2(:, i) = xx(1:66); -end -hrf2 = Xtrue2 * [1 0 0]'; - -xsecs = 0:.5:32; - -hrf1 = [ 0; 0; hrf1]; -hrf1 = hrf1(1:length(xsecs)); -hrf1 = hrf1 ./ max(hrf1); - -hrf2 = [ 0; 0; hrf2]; -hrf2 = hrf2(1:length(xsecs)); -hrf2 = hrf2 ./ max(hrf2); - -figure; plot(xsecs, hrf1, 'k') -hold; plot(xsecs, hrf2, 'g') - - -%hrf = hrf(1:4:end); % downsample to TR, if TR is > 0.5 - -% Create stimuli - -R = randperm(640); -t1 = 1:18; -t2 = 19:36; -R1 = sort(R(t1)); -R2 = sort(R(t2)); - -Run1 = zeros(640,1); -Run2 = zeros(640,1); -for i=1:length(R1), Run1(R1(i)) = 1; Run2(R2(i)) = 1; end; - -% Create timecourse - -beta1 = 1; beta2 = 0.8; -true_sig = beta1*conv(Run1, hrf1) + beta2*conv(Run2, hrf2); -true_sig = true_sig(1:640); - -tc_noise = noise_arp(640, [.3 0]); -tc = true_sig + 0.5 * tc_noise; -% tc = true_sig; -%figure; plot(tc); - - -Runc{1} = Run1; -Runc{2} = Run2; - - -%% - -create_figure; subplot(3,1,1); han = plot(tc); -title('Sample time course'); drawnow - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% -% Settings -% - -TR = 0.5; -%T = round(30/TR); -T = 30; -t = 1:TR:T; % samples at which to get Logit HRF Estimate -FWHM = 4; % FWHM for residual scan -pval = 0.01; -df = 600; -alpha = 0.001; - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% -% Create stick function (sample event onsets) -% Variable R contains onset times -% Variable Run contains stick (a.k.a. delta or indicator) function - -% R = [3, 21, 56, 65, 109, 126, 163, 171, 216, 232, 269, 282, 323, 341, 376, 385, 429, 446, 483, 491, 536, 552, 589, 602]; -% Run = zeros(640,1); -% for i=1:length(R), Run(R(i)) = 1; end; -% - -try - hold on; - hh = plot_onsets(R1,'r',-3,1, 1); - hh = plot_onsets(R2,'g',-3,1, 1); - drawnow -catch - disp('Couldn''t find function to add onset sticks to plot. Skipping.') -end - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Fit HRF using IL-function - -% Choose mode (deterministic/stochastic) - -mode = 0; % 0 - deterministic aproach - % 1 - simulated annealing approach - % Please note that when using simulated annealing approach you - % may need to perform some tuning before use. - -[h1, fit1, e1, param] = Fit_Logit2(tc,TR,Runc,T,mode); -[pv sres sres_ns1] = ResidScan(e1, FWHM); -[PowLoss1] = PowerLoss(e1, fit1, (len-7) , tc, TR, Runc, alpha); - -hold on; han(2) = plot(fit1,'r'); - -disp('Summary: IL_function'); - -disp('Amplitude:'); disp(param(1,:)); -disp('Time-to-peak:'); disp(param(2,:)*TR); -disp('Width:'); disp(param(3,:)*TR); - -disp('MSE:'); disp((1/(len-1)*sum(e1.^2))); -disp('Mis-modeling:'); disp(pv); -disp('Power Loss:'); disp(PowLoss1); - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Fit HRF using FIR-model - -% Choose mode (FIR/sFIR) - -mode = 1; % 0 - FIR - % 1 - smooth FIR - -[h2, fit2, e2, param] = Fit_sFIR(tc,TR,Runc,T,mode); -[pv sres sres_ns2] = ResidScan(e2, FWHM); -[PowLoss2] = PowerLoss(e2, fit2, (len-T) , tc, TR, Runc, alpha); - -hold on; han(3) = plot(fit2,'g'); - -disp('Summary: FIR'); - -disp('Amplitude'); disp(param(1,:)); -disp('Time-to-peak'); disp(param(2,:)*TR); -disp('Width'); disp(param(3,:)*TR); - -disp('MSE:'); disp((1/(len-1)*sum(e2.^2))); -disp('Mis-modeling'); disp(pv); -disp('Power Loss:'); disp(PowLoss2); - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Fit HRF using Canonical HRF + 2 derivatives - -p=1; - -[h3, fit3, e3, param, info] = Fit_Canonical_HRF(tc,TR,Runc,30,p); -[pv sres sres_ns3] = ResidScan(e3, FWHM); -[PowLoss3] = PowerLoss(e3, fit3, (len-p) , tc, TR, Runc, alpha); - -hold on; han(4) = plot(fit3,'m'); - - -disp('Summary: Canonical + 2 derivatives'); - -disp('Amplitude'); disp(param(1,:)); -disp('Time-to-peak'); disp(param(2,:)*TR); -disp('Width'); disp(param(3,:)*TR); - -disp('MSE:'); disp((1/(len-1)*sum(e3.^2))); -disp('Mis-modeling'); disp(pv); -disp('Power Loss:'); disp(PowLoss3); - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Fit HRF using Bsplines - -[h4, fit4, e4, param] = Fit_Spline(tc, TR, Runc, T); - -[pv sres sres_ns4] = ResidScan(e4, FWHM); -[PowLoss4] = PowerLoss(e4, fit4, (len-p) , tc, TR, Runc, alpha); - -hold on; han(5) = plot(fit4,'b'); - - -disp('Summary: Spline'); - -disp('Amplitude'); disp(param(1,:)); -disp('Time-to-peak'); disp(param(2,:)*TR); -disp('Width'); disp(param(3,:)*TR); - -disp('MSE:'); disp((1/(len-1)*sum(e4.^2))); -disp('Mis-modeling'); disp(pv); -disp('Power Loss:'); disp(PowLoss4); - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Fit HRF using non-linear gamma - -[h5, fit5, e5, param] = Fit_NLgamma(tc, TR, Runc, T); - -[pv sres sres_ns5] = ResidScan(e5, FWHM); -[PowLoss5] = PowerLoss(e5, fit5, (len-p) , tc, TR, Runc, alpha); - -hold on; han(6) = plot(fit5,'b'); - -legend(han,{'Data' 'IL' 'sFIR' 'DD' 'Spline' 'NL'}) - - -disp('Summary: NL gamma'); - -disp('Amplitude'); disp(param(1,:)); -disp('Time-to-peak'); disp(param(2,:)*TR); -disp('Width'); disp(param(3,:)*TR); - -disp('MSE:'); disp((1/(len-1)*sum(e5.^2))); -disp('Mis-modeling'); disp(pv); -disp('Power Loss:'); disp(PowLoss5); - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%figure; -%% - -subplot(3,2,5); hold on; -plot(xsecs, beta1*hrf1, 'k') -plot(xsecs, beta2*hrf2, 'k') - -xsecs1 = xsecs(1:length(h1)); -han2 = plot(xsecs1, h1,'r'); - -xsecs2 = xsecs(1:length(h2)); -han2(3:4) = plot(xsecs2, h2,'g'); -xsecs3 = xsecs(1:length(h3)); -han2(5:6) = plot(xsecs3, h3,'m'); -xsecs4 = xsecs(1:length(h4)); -han2(7:8) = plot(xsecs4, h4,'b'); -xsecs5 = xsecs(1:length(h5)); -han2(9:10) = plot(xsecs5, h5,'y'); -legend(han2,{'IL1' 'IL2' 'sFIR1' 'sFIR2' 'DD1' 'DD2' 'Spline1' 'Spline2' 'NL1' 'NL2'}) - -title('Estimated HRF'); - - -subplot(3,1,2); hold on; -hh = plot_onsets(R1,'r',-3,1, 1); -hh = plot_onsets(R2,'g',-3,1, 1); -drawnow - -han3 = plot(sres_ns1,'r'); -hold on; han3(2) = plot(sres_ns2,'g'); -hold on; han3(3) = plot(sres_ns3,'m'); -hold on; han3(4) = plot(sres_ns4,'b'); -hold on; han3(5) = plot(sres_ns5,'y'); - -hold on; plot((1:len),zeros(len,1),'--k'); -legend(han3,{'IL' 'sFIR' 'DD' 'Spline' 'NL'}) -title('Mis-modeling (time course)'); - - -subplot(3,2,6); hold on; - -[s1] = Fit_sFIR(sres_ns1,TR,Runc,T,0); -[s2] = Fit_sFIR(sres_ns2,TR,Runc,T,0); -[s3] = Fit_sFIR(sres_ns3,TR,Runc,T,0); -[s4] = Fit_sFIR(sres_ns4,TR,Runc,T,0); -[s5] = Fit_sFIR(sres_ns5,TR,Runc,T,0); - -han4 = plot(s1(1:T),'r'); -hold on; han4(2) = plot(s2(1:T),'g'); -hold on; han4(3) = plot(s3(1:T),'m'); -hold on; han4(4) = plot(s4(1:T),'b'); -hold on; han4(5) = plot(s5(1:T),'y'); -hold on; plot((1:T),zeros(T,1),'--k'); - -legend(han4,{'IL' 'sFIR' 'DD' 'Spline' 'NL'}) -title('Mis-modeling (HRF)'); - -%% - - -figure; hold on; - -% plot(xsecs, beta1*hrf1, 'k--', 'LineWidth', 2) -% plot(xsecs, beta2*hrf2, 'k--', 'LineWidth', 2) -% -% xsecs1 = xsecs(1:length(h1)); -% han2 = plot(xsecs1, h1,'r', 'LineWidth', 2); -% xsecs2 = xsecs(1:length(h2)); -% han2(3:4) = plot(xsecs2, h2,'g', 'LineWidth', 2); -% xsecs3 = xsecs(1:length(h3)); -% han2(5:6) = plot(xsecs3, h3,'m', 'LineWidth', 2); -% xsecs4 = xsecs(1:length(h4)); -% han2(7:8) = plot(xsecs4, h4,'b', 'LineWidth', 2); -% xsecs5 = xsecs(1:length(h5)); -% han2(9:10) = plot(xsecs5, h5,'y', 'LineWidth', 2); -% -% legend(han2,{'IL1' 'IL2' 'sFIR1' 'sFIR2' 'DD1' 'DD2' 'Spline1' 'Spline2' 'NL1' 'NL2'}) -% title('Estimated HRF'); - -subplot 121 -hold -plot(xsecs, beta1*hrf1, 'k--', 'LineWidth', 2) - -xsecs1 = xsecs(1:length(h1)); -han2 = plot(xsecs1, h1(:,1),'r', 'LineWidth', 2); -xsecs2 = xsecs(1:length(h2)); -han2(2) = plot(xsecs2, h2(:,1),'g', 'LineWidth', 2); -xsecs3 = xsecs(1:length(h3)); -han2(3) = plot(xsecs3, h3(:,1),'m', 'LineWidth', 2); -xsecs4 = xsecs(1:length(h4)); -han2(4) = plot(xsecs4, h4(:,1),'b', 'LineWidth', 2); -xsecs5 = xsecs(1:length(h5)); -han2(5) = plot(xsecs5, h5(:,1),'y', 'LineWidth', 2); - -legend(han2,{'IL1' 'sFIR1' 'DD1' 'Spline1' 'NL1'}) -title('Estimated HRF'); - -subplot 122 -hold -plot(xsecs, beta2*hrf2, 'k--', 'LineWidth', 2) - -xsecs1 = xsecs(1:length(h1)); -han2 = plot(xsecs1, h1(:,2),'r', 'LineWidth', 2); -xsecs2 = xsecs(1:length(h2)); -han2(2) = plot(xsecs2, h2(:,2),'g', 'LineWidth', 2); -xsecs3 = xsecs(1:length(h3)); -han2(3) = plot(xsecs3, h3(:,2),'m', 'LineWidth', 2); -xsecs4 = xsecs(1:length(h4)); -han2(4) = plot(xsecs4, h4(:,2),'b', 'LineWidth', 2); -xsecs5 = xsecs(1:length(h5)); -han2(5) = plot(xsecs5, h5(:,2),'y', 'LineWidth', 2); - -legend(han2,{'IL2' 'sFIR2' 'DD2' 'Spline2' 'NL2'}) -title('Estimated HRF'); - diff --git a/CanlabCore/HRF_Est_Toolbox2/Fit_Canonical_HRF.m b/CanlabCore/HRF_Est_Toolbox2/Fit_Canonical_HRF.m deleted file mode 100644 index 9c63652f..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/Fit_Canonical_HRF.m +++ /dev/null @@ -1,163 +0,0 @@ -function [hrf, fit, e, param, info] = Fit_Canonical_HRF(tc, TR, Run, T, p, varargin) -% function [hrf, fit, e, param, info] = Fit_Canonical_HRF(tc,TR,Runs,T,p) -% -% Fits GLM using canonical hrf (with option of using time and dispersion derivatives)'; -% -% INPUTS: -% -% tc - time course -% TR - time resolution -% Runs - expermental design -% T - length of estimated HRF -% p - Model type -% -% Options: p=1 - only canonical HRF -% p=2 - canonical + temporal derivative -% p=3 - canonical + time and dispersion derivative -% -% OUTPUTS: -% -% hrf - estimated hemodynamic response function -% fit - estimated time course -% e - residual time course -% param - estimated amplitude, height and width -% info - struct containing design matrices, beta values etc -% -% Created by Martin Lindquist on 10/02/09 -% Last edited: 05/17/13 (ML) - -% Edited to allow passing in custom design matrix e.g., from SPM. Will -% assume that the first regressor columns of the design matrix pertain to -% the regressors in Run: Michael Sun, Ph.D. 02/20/2024 - -[h, dh, dh2] = CanonicalBasisSet(TR); -%tc = tc'; -d = length(Run); -len = length(Run{1}); -% Generate a design matrix -t=1:TR:T; - -% Import your own design matrix -if ~isempty(varargin) - X=varargin{1}; - if numel(varargin)>1 - for i = 1:numel(varargin) - if strcmpi(varargin{i}, 'invertedDX') - PX=varargin{i+1}; - b=PX*tc; - end - end - end - if ~exist('b', 'var') - b = pinv(X)*tc; - end - - e = tc-X*b; - fit = X*b; - - % Be careful here. if p>1, make sure Run includes derivatives so there - % are p*task regressors. - % b = reshape(b(1:numel(Run)),p,d)'; % Extract my own regressors - - if numel(b) < p*d - error('Not enough beta weights to reshape into %d x %d.', d, p); - end - b = reshape(b(1:p*d), p, d)'; - - bc = zeros(d,1); - -else - - % Constructing the Design Matrix X: - X = zeros(len,p*d); - param = zeros(3,d); - - for i=1:d, - v = conv(Run{i},h); - X(:,(i-1)*p+1) = v(1:len); - - % Computing the first derivative - if (p>1) - v = conv(Run{i},dh); - X(:,(i-1)*p+2) = v(1:len); - end - - % Computing the second derivative - if (p>2) - v = conv(Run{i},dh2); - X(:,(i-1)*p+3) = v(1:len); - end - end - - % This line adds an intercept - X = [(zeros(len,1)+1) X]; - PX = pinv(X); - b = PX*tc; - e = tc-X*b; - fit = X*b; - - b = reshape(b(2:end),p,d)'; - bc = zeros(d,1); -end - -for i=1:d, - if (p == 1) - bc(i) = b(i,1); - H = h; - elseif (p==2) - bc(i) = sign(b(i,1))*sqrt((b(i,1))^2 + (b(i,2))^2); - H = [h dh]; - elseif (p>2) - bc(i) = sign(b(i,1))*sqrt((b(i,1))^2 + (b(i,2))^2 + (b(i,3))^2); - H = [h dh dh2]; - end -end - - -hrf = H*b'; - -for i=1:d, - param(:,i) = get_parameters2(hrf(:,i),1:length(t)); -end; - - -info ={}; -info.b = b; -info.bc = bc; -info.DX = X; -info.PX = PX; -info.H =H; - -end - -% END MAIN FUNCTION -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% -% Subfunctions -% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -function [h, dh, dh2] = CanonicalBasisSet(TR) - -len = round(30/TR); -xBF.dt = TR; -xBF.length= len; -xBF.name = 'hrf (with time and dispersion derivatives)'; -xBF = spm_get_bf(xBF); - -v1 = xBF.bf(1:len,1); -v2 = xBF.bf(1:len,2); -v3 = xBF.bf(1:len,3); - -h = v1; -dh = v2 - (v2'*v1/norm(v1)^2).*v1; -dh2 = v3 - (v3'*v1/norm(v1)^2).*v1 - (v3'*dh/norm(dh)^2).*dh; - -h = h./max(h); -dh = dh./max(dh); -dh2 = dh2./max(dh2); - -end \ No newline at end of file diff --git a/CanlabCore/HRF_Est_Toolbox2/Fit_Logit2.m b/CanlabCore/HRF_Est_Toolbox2/Fit_Logit2.m deleted file mode 100644 index 451b7122..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/Fit_Logit2.m +++ /dev/null @@ -1,58 +0,0 @@ -function [hrf, fit, e, param] = Fit_Logit2(tc, TR, Run, T, mode) -% function [hrf, fit, e, param] = Fit_Logit(tc,Run,t,mode) -% -% Fits FIR and smooth FIR model -% -% INPUTS: -% -% tc - time course -% TR - time resolution -% Runs - expermental design -% T - length of estimated HRF -% mode - deterministic or stochastic -% options: -% 0 - deterministic aproach -% 1 - simulated annealing approach -% Please note that when using simulated annealing approach you -% may need to perform some tuning before use. -% -% OUTPUTS: -% -% hrf - estimated hemodynamic response function -% fit - estimated time course -% e - residual time course -% param - estimated amplitude, height and width -% -% Created by Martin Lindquist on 10/02/09 -% Last edited: 05/31/13 (ML) - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Fit the Logit model - -numstim = length(Run); -len = length(Run{1}); -t=1:TR:T; - -V0 = [ 1 6 1 0.5 10 1 15]; % initial values for logit fit -V0 = repmat(V0,1,numstim); - -if (mode == 1 && numstim>1), - disp('Multi-stimulus annealing function currently not implemented. Switching to "deterministic mode"') - mode = 0; -end; - -% Estimate theta (logit parameters) - -if (mode == 1) - disp('Stochastic Mode'); - - Runs = Run{1}; - [theta,HH,C,P,hrf,fit,e,param] = Anneal_Logit(V0,t,tc,Runs); - -elseif (mode == 0) -% disp('Deterministic Mode'); - [theta, hrf, fit, e, param] = Det_Logit(V0,t,tc,Run); - -end - -end diff --git a/CanlabCore/HRF_Est_Toolbox2/Fit_NLgamma.m b/CanlabCore/HRF_Est_Toolbox2/Fit_NLgamma.m deleted file mode 100644 index d380f626..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/Fit_NLgamma.m +++ /dev/null @@ -1,115 +0,0 @@ -function [hrf, fit, e, param] = Fit_NLgamma(tc, TR, Run, T ) -% -% Fits non-linear Gamma HRF model -% -% INPUTS: -% -% tc - time course -% TR - time resolution -% Runs - expermental design -% T - length of estimated HRF -% -% OUTPUTS: -% -% hrf - estimated hemodynamic response function -% fit - estimated time course -% e - residual time course -% param - estimated amplitude, height and width -% -% By Martin Lindquist -% Created by Martin Lindquist on 03/13/23 - - -numstim = length(Run); -len = length(Run{1}); -t=1:TR:T; - -V0 = repmat([1 6 1]',1,numstim); % [Height, Delay, Onset]; - -% Find optimal values - -options = optimset('MaxFunEvals',10000,'Maxiter',10000,'TolX',1e-6,'TolFun',1e-6,'Display','off'); -VM = fminsearch(@msq_nl_gamma,V0,options,Run,TR, T,tc); - -% Use optimal values to fit hemodynamic response functions -hrf =zeros(length(t),numstim); -fitt = zeros(len,numstim); -param = zeros(3,numstim); - -for g = 1:numstim - hrf(:,g) = NL_gamma(TR, T, VM(:,g)); % Calculate HRF estimate (fit, given theta) - param(:,g) = get_parameters2(hrf(:,g),t); - fits = conv(Run{g}, hrf(:,g)); - fitt(:,g) = fits(1:len); -end - -fit = sum(fitt,2); -e = tc-fit; -%fit = fit + b0; - -end - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% -% SUBFUNCTIONS -% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -function m=msq_nl_gamma(V, Run, TR, T, tc) - -numstim = length(Run); -len = length(Run{1}); -t=1:TR:T; -h = zeros(length(t),numstim); -yhatt =zeros(len,numstim); - -for k = 1:numstim - h(:,k) = NL_gamma(TR, T, V(:,k)); % Get NL gamma model corresponding to parameters V - yhat = conv(Run{k}, h(:,k)); % Convolve IL model with stick function - yhatt(:,k) = yhat(1:len); -end - -yhat2 = sum(yhatt,2); %Sum models together to get overall estimate - -m = sum((tc-yhat2).^2); % Calculate cost function - -end - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -function [h] = NL_gamma(TR, T, V) -% inverse logit -- creates fitted curve from parameter estimates -% -% t = vector of time points -% V = parameters - -% 3 logistic functions to be summed together - -height = V(1); -%delay = 6; -delay = V(2); -udelay = 16; -%dispersion = 1; -dispersion = V(3); -udisp = 1; -rtou = 6; -onset = 1; -klength = T-1; - - % p(1) - delay of response (relative to onset) 6 - % p(2) - delay of undershoot (relative to onset) 16 - % p(3) - dispersion of response 1 - % p(4) - dispersion of undershoot 1 - % p(5) - ratio of response to undershoot 6 - % p(6) - onset (seconds) 0 - % p(7) - length of kernel (seconds) 32 - -normhrf = spm_hrf(TR,[delay udelay dispersion udisp rtou onset klength]); -h = height.*normhrf ./ max(normhrf); - - -end - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/CanlabCore/HRF_Est_Toolbox2/Fit_Spline.m b/CanlabCore/HRF_Est_Toolbox2/Fit_Spline.m deleted file mode 100644 index 898066de..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/Fit_Spline.m +++ /dev/null @@ -1,62 +0,0 @@ -function [hrf, fit, e, param] = Fit_Spline(tc, TR, Run, T) -% function [hrf, fit, e, param] = Fit_Spline(tc, TR, Run, T) -% -% Fits Spline model -% -% INPUTS: -% -% tc - time course -% TR - time resolution -% Runs - expermental design -% T - length of estimated HRF -% -% OUTPUTS: -% -% hrf - estimated hemodynamic response function -% fit - estimated time course -% e - residual time course -% param - estimated amplitude, height and width -% -% Created by Martin Lindquist on 03/06/23 - -numstim = length(Run); % Number conditions -len = length(Run{1}); % length of run -t=1:TR:T; -tlen = length(t); % Number of time points in HRF - -K = 8; % Number of b-spline basis (This cxan be set to specification) -norder = 4; % Order of b-spline basis (This cxan be set to specification) - -% Create design matrix -basis = create_bspline_basis([0,tlen], K+3, norder); -B = eval_basis((1:tlen),basis); -B = B(:,3:end-1); - -Wi = zeros(len, numstim*K); -for j=1:numstim - Wji = tor_make_deconv_mtx3(Run{j},tlen,1); - Wi(:,(j-1)*K+1:j*K) = Wji(:,1:tlen)*B; -end - -X = [ones(len,1) Wi]; - -% Fit model -b = pinv(X)*tc; -e = tc-X*b; -fit = X*b; - -b2 = reshape(b(2:end),K,numstim); - - -% Get parameters - -hrf = B*b2; - -for i=1:numstim - param(:,i) = get_parameters2(hrf(:,i),1:length(t)); -end - -end - -% END MAIN FUNCTION -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/CanlabCore/HRF_Est_Toolbox2/Fit_sFIR.m b/CanlabCore/HRF_Est_Toolbox2/Fit_sFIR.m deleted file mode 100644 index f9727d21..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/Fit_sFIR.m +++ /dev/null @@ -1,75 +0,0 @@ -function [hrf, fit, e, param] = Fit_sFIR(tc, TR, Run, T, mode) -% function [hrf, fit, e, param] = Fit_sFIR(tc,TR,Runs,T,mode) -% -% Fits FIR and smooth FIR model -% -% INPUTS: -% -% tc - time course -% TR - time resolution -% Runs - expermental design -% T - length of estimated HRF -% mode - FIR or smooth FIR -% options: -% 0 - standard FIR -% 1 - smooth FIR -% -% OUTPUTS: -% -% hrf - estimated hemodynamic response function -% fit - estimated time course -% e - residual time course -% param - estimated amplitude, height and width -% -% Created by Martin Lindquist on 10/02/09 -% Last edited: 05/17/13 (ML) - -numstim = length(Run); -len = length(Run{1}); -t=1:TR:T; -tlen = length(t); - -Runs = zeros(len,numstim); -for i=1:numstim - Runs(:,i) = Run{i}; -end - -[DX] = tor_make_deconv_mtx3(Runs,tlen,1); - -if mode == 1 - - C=(1:tlen)'*(ones(1,tlen)); - h = sqrt(1/(7/TR)); % 7 seconds smoothing - ref. Goutte - - v = 0.1; - sig = 1; - - R = v*exp(-h/2*(C-C').^2); - RI = inv(R); - MRI = zeros(numstim*tlen+1); - for i=1:numstim, - MRI(((i-1)*tlen+1):(i*tlen),((i-1)*tlen+1):(i*tlen)) = RI; - end; - - b = inv(DX'*DX+sig^2*MRI)*DX'*tc; - fit = DX*b; - e = tc - DX*b; - -elseif mode == 0 - - b = pinv(DX)*tc; - fit = DX*b; - e = tc - DX*b; - -end - - -hrf =zeros(tlen,numstim); -param = zeros(3,numstim); - -for i=1:numstim, - hrf(:,i) = b(((i-1)*tlen+1):(i*tlen))'; - param(:,i) = get_parameters2(hrf(:,i),(1:tlen)); -end; - -end \ No newline at end of file diff --git a/CanlabCore/HRF_Est_Toolbox2/Fit_sFIR_epochmodulation.m b/CanlabCore/HRF_Est_Toolbox2/Fit_sFIR_epochmodulation.m deleted file mode 100644 index 38c9558c..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/Fit_sFIR_epochmodulation.m +++ /dev/null @@ -1,393 +0,0 @@ -function [hrf, fit, e, param, DXinfo] = Fit_sFIR_epochmodulation(tc, TR, Run, T, mode, varargin) -% -% Fits FIR and smooth FIR model -% -% INPUTS: -% ---------- -% tc : (scan_length x 1) double vector -% time course of a single voxel. -% -% TR : float -% time resolution of one scan. -% e.g. use `0.5` for a scan collected every 0.5 seconds. -% -% Runs : cell array (1 x num_condition) -% experimental design matrix. each cell, the size of (1 x num_condition) contains -% a (scan_length x 1) double vector representing the design for that condition. -% -% T : double array -% desired length of the estimated HRF. indicates the number of beta -% coefficients to estimate, for each condition. -% e.g. [30, 30, 30, 30, 30, 30, 20, 12] for a design with 8 conditions. -% -% mode : int {0, 1} -% options for FIR. -% 0 - Standard FIR. -% 1 - Smooth FIR. -% -% -% OUTPUTS: -% ------- -% hrf : double array (time_point x num_condition) -% estimated hemodynamic response function. -% estimated HRF length differs depending on inputted T, across conditions. -% to account for this difference, the hrf matrix is the size of the largest time -% points estimated, which is max(T/TR). the shorter events are filled with NaNs -% -% fit : double array (scan_length x 1) -% estimated time course. -% -% e : double array (scan_length x 1) -% residual time course. -% -% param : double array (3 x num_condition) -% estimated amplitude, height and width -% -% Created by Martin Lindquist on 10/02/09 -% Last edited: 05/17/13 (ML) -% -% [10/27/2023 - HJ v1.2]: BUGFIX: remove redundant intercept due to multiple design matrix estimation -% - due to horzcat, we will have multiple intercepts in this design matrix -% - therefore, we find the intercepts, drop them, and add an intercept at the end of the matrix - -% [10/26/2023 - HJ v1.2]: Updated the code to Allow for Different Epoch Lengths -% - handles varying epoch lengths with the addition of T vector, instead of a scalar T -% - made adjustments accordingly to handle T vector in variable `tlen_all` -% - based on varying epoch lengths, dynamically calculates matrix using `tor_make_deconv_mtx3.m` -% - (dynamic matrix is concatenated into one design matrix at the end) -% - made adjustments accordingly to the estimated hrf variable: instead of being a vector, hrf is now a matrix with varying lengths of hrf estimation, due to varying epoch length inputted via T. -% - lastly, varying lengths of hrf is concatenated into one matrix (double: longest length hrf estimation x number of conditions) - -% [2/19/2024 - MS v2.0]: Updated the code to output design-matrix information, and allow input of multiple tc, -% pass in a custom design matrix, and to pass in an SPM structure - -% Debugging Hack, import your own Design Matrix from SPM - MS -if ~isempty(varargin) - if isstruct(varargin{1}) - % 1. Decide what are regressors and what are covariates here - % This is very difficult since I will have to pass in g, K, and W - % all from SPM.mat - - % So ultimately we will want to concatenate the FIR task regressors - % and the unfiltered covariates, and then filter the whole thing. - - SPM=varargin{1}; - - if numel(SPM.Sess)>1 && ~iscell(tc) % If SPM has concatenated runs but there's only one time series - error('SPM structures concatenating multiple runs must have timecourses passed in with a matching number of cell arrays'); - elseif iscell(tc) && numel(SPM.Sess)~=numel(tc) - error('Incompatible number of runs in SPM and in cell-array tc') - end - - if isempty(T) - for i = 1:numel(SPM.Sess) - T{i}=cellfun(@(cellArray) 2*ceil(max(cellArray)), {SPM.Sess(i).U.dur}); - end - elseif numel(SPM.Sess)>1 && ~iscell(T) - error('SPM structures concatenating multiple runs must have time windows passed in with a matching number of cell arrays.'); - elseif iscell(T) && numel(SPM.Sess)~=numel(T) - error('Incompatible number of runs in SPM and in cell-array T.') - end - - for s = 1:numel(SPM.Sess) - len = numel(SPM.Sess(s).row); - - % Task regressors for each run can be found here: - numstim=numel(SPM.Sess(s).U); - TR = SPM.xY.RT; - - % Make the design matrix: - DX_all = cell(1, numstim); % Store DX matrices for each condition - tlen_all = zeros(1, numstim); % Store tlen for each condition - Runs{s}=generateConditionTS(numel(SPM.Sess(s).row), [SPM.Sess(s).U.name], {SPM.Sess(s).U.ons}, {SPM.Sess(s).U.dur}); - - for i=1:numstim - t = 1:TR:T{s}(i); - tlen_all(i) = length(t); - DX_all{i} = tor_make_deconv_mtx3(Runs{s}(:,i), tlen_all(i), 1); - end - DX = horzcat(DX_all{:}); - - % due to horzcat, we will have multiple intercepts in this design matrix - % therefore, we'll find the intercepts, drop them, and add an intercept at the end of the matrix - intercept_idx = find(sum(DX)==len); - copyDX = DX; - copyDX(:,intercept_idx) = []; - DX = [copyDX ones(len,1)]; - - % Covariate Design Matrix for each session (without intercept): - NX=[SPM.Sess(s).C.C]; - - % Concatenate the Task regressors with Covariates - X=[DX, NX]; - % Filter - DX_cov{s}=spm_filter(SPM.xX.K(s), X); - end - - if numel(DX_cov) > 1 - for i=1:numel(DX_cov) - [hrf{i}, fit{i}, e{i}, param{i}, DXinfo{i}]=Fit_sFIR_epochmodulation(tc{i}, TR, Runs{i}, T{i}, mode, DX_cov{i}); - end - return; - end - - elseif isnumeric(varargin{1}) - % If not an SPM struct, allow a design matrix to be passed in. - DX_cov=varargin{1}; - - numstim = length(Run); - tlen_all = zeros(1, numstim); % Store tlen for each condition - for i=1:numstim - t = 1:TR:T(i); - tlen_all(i) = length(t); - end - end - - for i=1:numel(varargin) - % If you pass in both a DX and an inverted DX, this will speed - % things up a bit. - if strcmpi(varargin{i}, 'invertedDX') - PX=varargin{i+1}; - b = PX*tc; - if exist('DX_cov', 'var') - fit = DX_cov*b; - e = tc - DX_cov*b; - DXinfo.DX=DX_cov; - DXinfo.PX=DX_cov; - else - fit = 'DX_cov not passed in'; - e = 'DX_cov not passed in'; - DXinfo.DX=[]; - DXinfo.PX=PX; - end - end - - end - - - - % Processing of varargin done. - - % sFIR - if mode == 1 && ~exist('b', 'var') - % pen = {toeplitz([1 .3 .1])} - % n_nuis = 20; % num of nuisance - % pen{2} = zeros(20); % for nuisance, add no penalty - % blkdiag(pen{:}) - - MRI = zeros(sum(tlen_all)+1); % adjust size based on varying tlen and intercept at end - start_idx = 1; - - for i=1:numstim - tlen = tlen_all(i); % get the tlen for this stimulus - - C = (1:tlen)'*(ones(1,tlen)); - h = sqrt(1/(7/TR)); - - v = 0.1; - sig = 1; - - R = v*exp(-h/2*(C-C').^2); - RI = inv(R); - - % Adjust the indices to account for varying tlen - end_idx = start_idx + tlen - 1; - MRI(start_idx:end_idx, start_idx:end_idx) = RI; - - start_idx = end_idx + 1; % update the starting index for next iteration - end - - % multiply a 0 penalty with NX. - cov_num = size(DX_cov,2)-size(MRI,2); - % pen = sig^2*MRI; % Regularization Penalty Matrix - % pen=pad(pad(pen, cov_num)',cov_num)'; - % pen{2} = zeros(20); % for nuisance, add no penalty - pen{1} = sig^2*MRI; % Regularization Penalty Matrix - pen{2} = zeros(cov_num); % for nuisance, add no penalty - pen=blkdiag(pen{:}); - - % disp(pen) % Check what this looks like. - % X = [DX, NX]; % Full Design Matrix - - PX=inv(DX_cov'*DX_cov+pen)*DX_cov'; - b = PX*tc; - % b = DX'*tc; - fit = DX_cov*b; - e = tc - DX_cov*b; - DXinfo.DX=DX_cov; - DXinfo.PX=PX; - - elseif mode == 0 && ~exist('b', 'var') - PX=pinv(DX_cov); - b = PX*tc; - fit = DX_cov*b; - e = tc - DX_cov*b; - DXinfo.DX=DX_cov; - DXinfo.PX=PX; - - end - -else - - % If nothing is passed in varargin: - - - numstim = length(Run); - len = length(Run{1}); - - Runs = zeros(len,numstim); - for i=1:numstim - Runs(:,i) = Run{i}; - end - - DX_all = cell(1, numstim); % Store DX matrices for each condition - tlen_all = zeros(1, numstim); % Store tlen for each condition - - - for i=1:numstim - t = 1:TR:T(i); - tlen_all(i) = length(t); - DX_all{i} = tor_make_deconv_mtx3(Runs(:,i), tlen_all(i), 1); - end - DX = horzcat(DX_all{:}); - - % due to horzcat, we will have multiple intercepts in this design matrix - % therefore, we'll find the intercepts, drop them, and add an intercept at the end of the matrix - intercept_idx = find(sum(DX)==len); - copyDX = DX; - copyDX(:,intercept_idx) = []; - DX = [copyDX ones(len,1)]; - - if mode == 1 - - MRI = zeros(sum(tlen_all)+1); % adjust size based on varying tlen and intercept at end - start_idx = 1; - - for i=1:numstim - tlen = tlen_all(i); % get the tlen for this stimulus - - C = (1:tlen)'*(ones(1,tlen)); - h = sqrt(1/(7/TR)); - - v = 0.1; - sig = 1; - - R = v*exp(-h/2*(C-C').^2); - RI = inv(R); - - % Adjust the indices to account for varying tlen - end_idx = start_idx + tlen - 1; - MRI(start_idx:end_idx, start_idx:end_idx) = RI; - - start_idx = end_idx + 1; % update the starting index for next iteration - end - - % DX - % - % NX = ;% Nuisance covariates and intercepts - - % X = [DX, NX]; % Full Design Matrix - pen = sig^2*MRI; % Regularization Penalty Matrix - % disp(pen) % Check what this looks like. - cov_num = size(DX,2)-size(pen, 2); - - pen=canlab_pad(canlab_pad(pen, cov_num)',cov_num)'; - - % multiply a 0 penalty with NX. - - % gKW the DX Design Matrix - - - % b = inv(DX'*DX+sig^2*MRI)*DX'*tc; - PX=inv(DX'*DX+pen)*DX'; - b = PX*tc; - fit = DX*b; - e = tc - DX*b; - - % Code added by Michael Sun, PhD 02/05/2024 - % DXinfo.DX=inv(DX'*DX+sig^2*MRI)*DX'; % This DX is essentially regression coefficients - DXinfo.DX=DX; - DXinfo.PX=PX; - - % DXinfo.DX=DX; - DXinfo.sig=sig; - DXinfo.MRI=MRI; - - elseif mode == 0 - PX =pinv(DX); - b = PX*tc; - fit = DX*b; - e = tc - DX*b; - - % Coded added by Michael Sun, Ph.D. - DXinfo.DX=DX; - DXinfo.PX=PX; - - end - - - -end - -numstim = length(tlen_all); -hrf = cell(1, numstim); - -for i = 1:numstim - hrf{i} = zeros(tlen_all(i), numstim); -end - -% hrf =zeros(tlen,numstim); -param = zeros(3,numstim); - -for i=1:numstim - % hrf{:,i} = b(((i-1)*tlen_all(i)+1):(i*tlen_all(i)))'; % Buggy line Edit is below: Michael Sun, 10/27/2023 - start_idx = sum(tlen_all(1:i-1)) + 1; - end_idx = start_idx + tlen_all(i) - 1; - hrf{:,i} = b(start_idx:end_idx)'; - param(:,i) = get_parameters2(hrf{:,i}, (1:tlen_all(i))); - - DXinfo.tlen{i}=[start_idx:end_idx]; -end - -% HJ: concatenate estimated hrf -% since different event lengths have different lengths of hrf estimation, -% we'll identify the maximum length of hrf estimation timepoints; -% for the shorter events, we'll pad them with zeros. -maxLen = max(cellfun(@length, hrf)); -resultMatrix = NaN(maxLen, numstim); % Pre-allocate a matrix of NaN values -for i = 1:numel(hrf) - resultMatrix(1:length(hrf{i}), i) = hrf{i}'; -end -hrf = resultMatrix; -end - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% validation plots: design matrix -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% figure; -% imagesc(DX); % Display matrix as an image -% colorbar; % Add colorbar to the figure -% title('Heatmap of DX'); -% xlabel('Columns of DX'); -% ylabel('Rows of DX'); -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% % validation estimated curve -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% numCells = length(hrf)'; -% % Initialize a figure -% figure; -% for i = 1:numCells -% % Create a subplot for each cell -% subplot(2, 4, i); % Assuming a 2x4 grid for 8 panels - -% % Plot the values of the double array -% plot(hrf{i}, 'o-'); - -% % Label axes and provide title for each subplot -% xlabel('# of time points'); -% ylabel('Voxel-wise Amplitude'); -% title([num2str(length(hrf{i})) ' time points estimated']); % Updated title - -% % Adjust y-axis for better visualization -% ylim([min(hrf{i})-0.1, max(hrf{i})+0.1]); -% end diff --git a/CanlabCore/HRF_Est_Toolbox2/Get_Logit.m b/CanlabCore/HRF_Est_Toolbox2/Get_Logit.m deleted file mode 100644 index 6a4c3dfe..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/Get_Logit.m +++ /dev/null @@ -1 +0,0 @@ -function [h, base] = Get_Logit(V,t) % % [h] = get_logit(V,t) % % Calculate inverse logit (IL) HRF model % Creates fitted curve - 3 logistic functions to be summed together - from parameter estimates % % INPUT: V, t % t = vector of time points % V = parameters % % By Martin Lindquist and Tor Wager % Edited 12/12/06 % A1 = V(1); T1 = V(2); d1 = V(3); A2 = V(4); T2 = V(5); A3 = V(6); T3 = V(7); d2 = -d1*(ilogit(A1*(1-T1)) - ilogit(A3*(1-T3)))/(ilogit(A2*(1-T2)) + ilogit(A3*(1-T3))); d3 = abs(d2)-abs(d1); h = d1*ilogit(A1*(t-T1))+d2*ilogit(A2*(t-T2))+d3*ilogit(A3*(t-T3)); % Superimpose 3 IL functions h = h'; base =zeros(3,length(t)); base(1,:) = ilogit(A1*(t-T1)); base(2,:) = ilogit(A2*(t-T2)); base(3,:) = ilogit(A3*(t-T3)); return \ No newline at end of file diff --git a/CanlabCore/HRF_Est_Toolbox2/HMHRFest.m b/CanlabCore/HRF_Est_Toolbox2/HMHRFest.m deleted file mode 100644 index b52f809a..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/HMHRFest.m +++ /dev/null @@ -1,602 +0,0 @@ -function [Res] = HMHRFest(y, Runs, TR, nbasis, norder) -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% HRF estimation algorithm -% -% By David Degras and Martin Lindquist -% Created: 08/22/12 -% Last edited: 03/27/14 -% -% INPUTS: -% -% y: Data matrix (#time points) by (#subjects) by (#voxels) -% Runs: Stick functions for each subject (#time points) by (#conditions) by (#subjects) -% TR: Time resolution -% nbasis: Number of b-spline basis -% norder: Order of b-spline basis - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Set initial values - -% TR = 1; % TR of experiment -% nbasis = 20; % Number of b-spline basis -% norder = 6; % Order of b-spline basis - -[len, sub, voxpar] = size(y); -[~, L, ~] = size(Runs); - -I = 1; % Number of groups -N1 = sub; % N1: Number of subjects in Group 1 -Tlen = 30/TR; % Length of HRF -q = 2; % Number of nuisance parameters -p = 1; % AR order -lambda = 50; % Smoothing parameter - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Info about the parcellation - -voxels = sum(voxpar); % Total number of voxels -parcels = length(voxpar); % Number of parcels -parind = zeros(voxpar(1),1)+1; - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Create a struct containing all initial values - -Init = {}; -Init.I = I; -Init.sub = sub; -Init.len = len; -Init.N1 = N1; -Init.L = L; -Init.TR = TR; -Init.Tlen = Tlen; -Init.nbasis = nbasis; -Init.norder = norder; -Init.q = q; -Init.p = p; -Init.lambda = lambda; -Init.voxels = voxels; -Init.parcels = parcels; -Init.voxpar = voxpar; -Init.parind = parind; - - -%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Generate basis sets for potential HRF shapes. - -hphi = zeros(3,nbasis); % Basis sets for 3 potenital HRF shapes -hf = CanonicalBasisSet(TR); - -% Create bspline basis set -basis = create_bspline_basis([0,Tlen], nbasis+8, norder); -B = eval_basis((1:Tlen),basis); -B = B(:,6:end-3); - - -Init.B = B; -SM = zeros(Tlen,3); -for i=1:3, - - Run = zeros(Tlen,1); - Run(1:(2*(i-1)+1)) = 1; - s = conv(hf,Run); - s = s(1:Tlen); - s = s./max(s); - - SM(:,i) = s; - tmp = (inv(B'*B)*B'*s)'; - hphi(i,:) = tmp./sum(tmp.^2); - -end - -hphi = orth(hphi')'; % Orthogonalize - -Init.hphi = hphi; - - -G0 = (inv(B'*B)*B'*hf); % Need these values for Hotellings test. - - - -%% - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Estimation of HRF and nuisance parameters - -Beta = zeros(L, voxels); % Voxel-wise beta -G = zeros(nbasis, voxels); % Voxel-wise gamma -K = zeros(N1*nbasis*L, voxels); % Voxel-wise ksi -% E = zeros(len, voxels); % Voxel-wise epsilon -Ar = zeros(p,voxels); % Voxel-wise AR coeficients -S2e = zeros(1,voxels); % Voxel-wise withing-subject variance -CC = zeros(1,voxels); % Voxel-wise Lagrange Multiplier - -% Create initial covariance matrix (indentity matrix for all subjects) - -iV = zeros(len,len,sub); -for j=1:sub - iV(:,:,j) = eye(len); -end - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Step 1: Pilot Estimation of the brain response -% First-pass estimation of beta and gamma for each voxel - -for v = 1:voxels - - [beta, gamma, C, AE, sig2e, ksiv, ~, XB, Phi, P] = EstimationVoxel(y(:,:,v), iV, Runs, Init); - - % Store results - Beta(:,v) = beta; - G(:,v) = gamma; - Ar(:,v) = AE'; - S2e(v) = sig2e; - K(:,v) = ksiv; - CC(:,v) = C; -% E(:,v) = epsv; - -end - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Step 2: Estimation of the temporal dependence - -A = zeros(len,len,parcels); -sig2_eps = zeros(1,parcels); -phi = zeros(p,parcels); - -% Estimate parcel-specific matrix Am (AR components) - -for m = 1:parcels - - ind = (parind == m); - % vind = find(ind); - phi(:,m) = mean(Ar(:,ind)); - sig2_eps(m) = mean(S2e(ind)); - - Atmp = eye(len); - for t=1:(len-1) - Atmp = Atmp + (phi.^t).*(diag(ones(len-t,1),t) + diag(ones(len-t,1),-t)); - end - A(:,:,m) = (1/(1-phi^2))*Atmp; - -end - - -% Estimate rho - -rho_l = zeros(nbasis,L,voxels); -sig2_ksi = zeros(L,voxels); -Tksi = zeros(L*nbasis,L*nbasis); - -for v=1:voxels - - Ksi2 = zeros(1,L); - KK = zeros(nbasis,nbasis,L); - Tksi_l = zeros(nbasis,nbasis,L); - - for j=1:N1, - - ksiv = K(:,v); - ksi = ksiv((j-1)*nbasis*L+1:(j*nbasis*L)); - ksi = reshape(ksi,nbasis,L); - - for l=1:L - Ksi2(l) = Ksi2(l) + ksi(:,l)'*ksi(:,l); - KK(:,:,l) = KK(:,:,l) + ksi(:,l)*ksi(:,l)'; - end - - end - - for l=1:L - - sig2_ksi(l,v) = Ksi2(l)/(N1*nbasis); - CVM = KK(:,:,l)/N1; - - [~,CM] = cov2corr(CVM); - - for k=1:nbasis - rho_l(k,l,v) = mean(diag(CM,(k-1))); - end - - Tksi_l(:,:,l) = toeplitz(rho_l(:,l,v)); - Tksi((l-1)*nbasis+1:l*nbasis, (l-1)*nbasis+1:l*nbasis, v) = Tksi_l(:,:,l); - - end - -end - - -rho = mean(rho_l,3); -Tksi = mean(Tksi,3); -rho_new = rho; - - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% EM-algorithm - -% START HERE - -maxiter = 10; -tol = 1e-6; -rho_norm = 100; -sig_norm = 100; -niter = 1; -%options = optimset('MaxFunEvals',1000,'Maxiter',1000,'TolX',1e-6,'TolFun',1e-6,'Display','off'); -options = optimset('MaxFunEvals',500,'Maxiter',500,'TolX',1e-6,'TolFun',1e-6,'Display','off'); - -while (rho_norm>=tol && sig_norm>=tol && (niter <= maxiter)) - - rho_tmp = zeros(nbasis,L,voxels); - Tksi_tmp = zeros(L*nbasis,L*nbasis,voxels); - - sig2_ksi_old = sig2_ksi; - rho_old = rho_new; - - for v=1:voxels - - if (sum(sig2_ksi ==0) == 0) - isig2k = 1./sig2_ksi(:,v); - else - isig2k = zeros(size(sig2_ksi)); - end - - m = parind(v); - iA = inv(A(:,:,m)); - isig2e = 1/sig2_eps(m); - iTksi = pinv(Tksi); - C = zeros(nbasis,nbasis,L); - - k2 = kron(diag(isig2k),eye(nbasis)); - kbg = kron(beta,gamma); - for j=1:N1 - - Bj = inv(XB(:,:,j)'*(isig2e*iA)*XB(:,:,j) + iTksi + k2); - Rj = eye(len) - Phi(:,:,j)*inv(Phi(:,:,j)'*iV(:,:,j)*Phi(:,:,j))*Phi(:,:,j)'*iV(:,:,j); - ksi = Bj*XB(:,:,j)'*(isig2e*iA)*Rj*(y(:,j,v) - XB(:,:,j)*kbg); - K((j-1)*L*nbasis+1:j*L*nbasis,v) = ksi; - - for l=1:L - C(:,:,l) = C(:,:,l) + (ksi((l-1)*nbasis+1:l*nbasis)*ksi((l-1)*nbasis+1:l*nbasis)'); % + Bj((l-1)*nbasis+1:l*nbasis, (l-1)*nbasis+1:l*nbasis)); - end - - end - - for l=1:L - dbstop if error - - rho_tmp(1,l,v) = 1; - rho_tmp(2:end,l,v) = fminsearch(@minQ,rho_old(2:end,l),options,diag(isig2k),N1,nbasis); - - Tksi_tmp((l-1)*nbasis+1:l*nbasis, (l-1)*nbasis+1:l*nbasis, v) = toeplitz(rho_new(:,l)); - sig2_ksi(l,v) = max(0, trace(pinv(Tksi((l-1)*nbasis+1:l*nbasis, (l-1)*nbasis+1:l*nbasis))*C(:,:,l))/(N1*nbasis)); - end - - end - - rho_new = mean(rho_tmp,3); - Tksi = mean(Tksi_tmp,3); - - rho_norm = norm(rho_new - rho_old); - sig_norm = norm(sig2_ksi - sig2_ksi_old); - niter = niter + 1; - -end - -% END HERE -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Step 3: Estimation of between-subject variance - - -AA = zeros(L,L); -for l1=1:L - for l2=1:L - for j=1:N1 - XBj1 = XB(:,(l1-1)*nbasis+1:l1*nbasis,j); - XBj2 = XB(:,(l2-1)*nbasis+1:l2*nbasis,j); - AA(l1,l2) = AA(l1,l2) + trace(XBj1*Tksi((l1-1)*nbasis+1:l1*nbasis, (l1-1)*nbasis+1:l1*nbasis)*XBj1'*XBj2*Tksi((l2-1)*nbasis+1:l2*nbasis, (l2-1)*nbasis+1:l2*nbasis)*XBj2'); - end - end -end -AA = (AA+AA')/2; - -for v =1:voxels - - bb = zeros(L,1); - m = parind(v); - - for j=1:N1 - - Rj = iV(:,:,j)*(eye(len) - Phi(:,:,j)*inv(Phi(:,:,j)'*iV(:,:,j)*Phi(:,:,j))*Phi(:,:,j)'); - r_j = Rj*y(:,j) - Rj*XB(:,:,j)*kron(Beta(:,v),G(:,v)); - - for l=1:L - XBj = XB(:,(l-1)*nbasis+1:l*nbasis,j); - bb(l) = bb(l) + (r_j'*XBj*Tksi((l-1)*nbasis+1:l*nbasis, (l-1)*nbasis+1:l*nbasis)*XBj'*r_j - sig2_eps(m)*trace(Tksi((l-1)*nbasis+1:l*nbasis, (l-1)*nbasis+1:l*nbasis)*XBj'*A(:,:,m)*XBj)); - end - end - sig2_ksi(:,v) = quadprog(AA,bb,[],[],[],[],zeros(L,1)); - -end - - - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Second-pass estimation of beta and gamma for each voxel - -% sig2_ksi = zeros(1,voxels); - -for v = 1:voxels - - % Compute inverse of total covariance matrix V - iV = zeros(len,len,sub); - D = diag(sig2_ksi(:,v)); - for j=1:sub - V = XB(:,:,j)*(kron(D,eye(nbasis)))*Tksi*XB(:,:,j)' + sig2_eps(parind(v))*A(:,:,m); - iV(:,:,j) = inv(V); - end - - % Second-pass estimate of beta and gamma -% [beta, gamma, C, AE, sig2e, ksiv, epsv, XB, Phi] = EstimationVoxel(y(:,:,v), iV, Runs, Init); - [beta, gamma, C] = EstimationVoxel(y(:,:,v), iV, Runs, Init); - Beta(:,v) = beta; - G(:,v) = gamma; - CC(:,v) = C; - K(:,v) = ksiv; -end - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Inference - -sbeta = zeros(L,voxels); -sgamma = zeros(nbasis,voxels); -HT = zeros(1,voxels); - -sbeta2 = zeros(L,voxels); -sbeta3 = zeros(L,voxels); - - -Beta2 = Beta; -whmax = zeros(size(Beta)); - -for v = 1:voxels - - M=0; - Ny =0; - for j=1:sub - Rj = eye(len) - Phi(:,:,j)*inv(Phi(:,:,j)'*iV(:,:,j)*Phi(:,:,j))*Phi(:,:,j)'*iV(:,:,j); - M = M + XB(:,:,j)'*Rj'*iV(:,:,j)*Rj*XB(:,:,j); - Ny = Ny + XB(:,:,j)'*iV(:,:,j)*Rj*y(:,j); - end - - ZB = kron(eye(L),G(:,v)); - VB = ZB'*M*ZB; - sbeta(:,v) = sqrtm(VB)*Beta(:,v); - - for l=1:L - hh = B*G; - tmp = hh*Beta(l,v); - [a,b] = max(abs(tmp)); - Beta2(l,v) = tmp(b); - whmax(l,v) = b; - end - - sbeta2(:,v) = sqrtm(VB)*Beta2(:,v); - - ZG = kron(Beta(:,v),eye(nbasis)); - VG = ZG'*M*ZG; - sgamma(:,v) = sqrtm(VG)*(G(:,v)-G0); - - HT(v) = (G(:,v)-G0)'*sqrtm(VG)*(G(:,v)-G0); - - -end - - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Bookkeeping -Res = {}; -Res.beta = Beta; % Voxel-wise beta -Res.gamma = G; % Voxel-wise gamma -Res.H = B*G; % Voxel-wise HRF estimates -% Res.Err = Err; -Res.AR = phi; -Res.S2e = sig2_eps; -% Res.K = K; -Res.S2k = sig2_ksi; -Res.rho = rho_new; - -Res.sbeta = sbeta; -Res.beta2 = Beta2; - -Res.sgamma = sgamma; -Res.sbeta2 = sbeta2; - -Res.whmax = whmax; -Res.HT = HT; -Res.K = K; - -% Res.sgamma2 = sgamma2; -% Res.SigmaE = SigmaE; -% Res.sPhi = sPhi; -% Res.Vbeta = Vb; -% Res.Vgamma = Vg; - - -end - -%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% -% SUBFUNCTIONS -% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - - -function Q=minQ(rho,D, N1,nbasis) - -rho = [1; rho]; - -Tksi = toeplitz(rho); - -Q = (N1*nbasis*log(det(D)) +N1*log(det(Tksi)) + N1); - -end - - -function [beta, gamma, C, AR, sig2e, ksiv, epsv, XB, Phi, P] = EstimationVoxel(y, iV, Runs, Init) - -% Initial values -nbasis = Init.nbasis; -%norder = Init.norder; -Tlen = Init.Tlen; -len = Init.len; -L = Init.L; -N1 = Init.N1; -q = Init.q; -p = Init.p; -lambda = Init.lambda; -hphi = Init.hphi; -B = Init.B; - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Create model matrices - - -% Create design matrix - -XB = zeros(len, nbasis*L , N1); -for j=1:N1 - Xj = []; - for l=1:L - Xjl = tor_make_deconv_mtx3(Runs(:,l,j),Tlen,1); - Xj = [Xj Xjl(:,1:Tlen)*B]; - end - - XB(:,:,j) = Xj; -end - -% Create nuisance matrix -Phi_ij = zeros(len,q); -Phi_ij(:,1) = 1; -Phi_ij(:,2) = (1:len)./len; -% Phi_ij(:,3) = Phi_ij(:,2).^2; -Phi_ij = orth(Phi_ij); % Orthogonalize matrix - -Phi = zeros(len, q, N1); -for j=1:N1 - Phi(:,:,j) = Phi_ij; -end - -%% - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Estimation algorithm - - -% Step 1: Compute penalty - -P = eye(nbasis); - -numh = size(hphi,1); -for q = 1:numh, - P = P - hphi(q,:)'*hphi(q,:); -end - - -% Step 2: Estimate initial value of gamma - -sXRX = 0; -sXRy = 0; - -for j=1:N1, - Rj = iV(:,:,j)*(eye(len) - Phi(:,:,j)*inv(Phi(:,:,j)'*iV(:,:,j)*Phi(:,:,j))*Phi(:,:,j)'*iV(:,:,j)); - sXRX = sXRX + XB(:,:,j)'*Rj*XB(:,:,j); - sXRy = sXRy + XB(:,:,j)'*Rj*y(:,j); -end - -h0 = inv(sXRX + N1*lambda*kron(eye(L),P))*sXRy; -h0 = reshape(h0,nbasis,L); -tmp = sum(h0,2); -gamma = tmp./norm(tmp,2); - -% Step 3: Iteratively estimate beta and gamma - -betaold = zeros(L,1); -beta = ones(L,1); -gammaold = zeros(size(gamma)); - - -cnt = 0; -while ((norm(gamma - gammaold) > 0.0001 || norm(beta-betaold) > 0.0001) && cnt < 1000) - betaold = beta; - gammaold = gamma; - cnt = cnt + 1; - - Z = kron(eye(L),gamma); - W1 = Z'*sXRX*Z; - W2 = Z'*sXRy; - beta = inv(W1'*W1)*W1'*W2; - - - Z = kron(beta,eye(nbasis)); - W1 = Z'*sXRX*Z + N1*lambda*P; - W2 = Z'*sXRy; - - [U,D,~] = svd(W1); - lam = diag(D); - - [C,~,flag] = fzero(@(C) sum(((W2'*U).^2)'./((lam+C).^2)) - 1, 0); - if (flag == -6) - C = 0.1; - end - - W1 = W1 + C*eye(nbasis); - gamma = inv(W1'*W1)*W1'*W2; - gamma = gamma./norm(gamma,2); - - if (sum(abs(beta)) < 0.001), cnt = 1000; end % End if all beta are close to zeros -end - -% Step 4: Estimation of temporal dependence - - -AR = zeros(N1,p); -sig2e = zeros(N1,1); -ksiv = zeros(N1*nbasis*L,1); -epsv = zeros(len,1); - -for j=1:N1, - - Rj = iV(:,:,j)*(eye(len) - Phi(:,:,j)*inv(Phi(:,:,j)'*iV(:,:,j)*Phi(:,:,j))*Phi(:,:,j)'); - r_j = Rj*y(:,j) - Rj*XB(:,:,j)*kron(beta,gamma); - ksi = inv(XB(:,:,j)'*XB(:,:,j) + 1000*eye(L*nbasis))*XB(:,:,j)'*r_j; - - epsilon = r_j; - epsv = epsv + epsilon; - - [a,e] = aryule(epsilon,p); - - AR(j,:) = -a(2:end); - sig2e(j) = e; - - ksiv((j-1)*nbasis*L+1:(j*nbasis*L)) = ksi; - -end - -AR = mean(AR); -sig2e = max(mean(sig2e),0); - -end \ No newline at end of file diff --git a/CanlabCore/HRF_Est_Toolbox2/New_fminsearchbnd/fminsearchbnd.m b/CanlabCore/HRF_Est_Toolbox2/New_fminsearchbnd/fminsearchbnd.m deleted file mode 100644 index 80910389..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/New_fminsearchbnd/fminsearchbnd.m +++ /dev/null @@ -1,326 +0,0 @@ -function [x,fval,exitflag,output]=fminsearchbnd4(fun,x0,LB,UB,options,varargin) -% FMINSEARCHBNDNEW: FMINSEARCH, but with bound constraints by transformation -% -% Changes from fminsearchbnd: -% 1) in options structure, user may pass an 'output function' and 'plot function' to fminsearch. -% Original fminsearchbnd handled the output function via a nested wrapper function. I have extended -% this to the plot function too. -% 2) I have moved the 'intrafun' and 'xtransform' functions and wrappers to be nested functions -% (INSIDE the fminsearchbnd function), so they do not need to pass the params structure around -% (into fminsearch) - but have access to it directly. This maintains the integrity of the varargin, -% which the user may be passing thru fminsearch to their optmization funciton (fminsearchbnd had -% passed the params structure to fminsearch, thus ruining any varargin that the user passed in). -% This also obviates the params.(whatever) structure the author had, so I've eliminated it so things -% are simpler. -% 3) I have created a test example so the user can see not only how fminseachbnd works, but also how -% the OutputFn and PrintFns functions work, which were heretofore poorly documented by MathWorks. -% Many thanks to the original author, John D'Errico, for excellent work - very useful! -% -% Modifications by: Ken Purchase -% Email: kpurchase at yahoo -% Date: 2007-Nov-29 -% -% -% usage: x=FMINSEARCHBND(fun,x0) -% usage: x=FMINSEARCHBND(fun,x0,LB) -% usage: x=FMINSEARCHBND(fun,x0,LB,UB) -% usage: x=FMINSEARCHBND(fun,x0,LB,UB,options) -% usage: x=FMINSEARCHBND(fun,x0,LB,UB,options,p1,p2,...) -% usage: [x,fval,exitflag,output]=FMINSEARCHBND(fun,x0,...) -% -% arguments: -% fun, x0, options - see the help for FMINSEARCH -% -% LB - lower bound vector or array, must be the same size as x0 -% -% If no lower bounds exist for one of the variables, then -% supply -inf for that variable. -% -% If no lower bounds at all, then LB may be left empty. -% -% Variables may be fixed in value by setting the corresponding -% lower and upper bounds to exactly the same value. -% -% UB - upper bound vector or array, must be the same size as x0 -% -% If no upper bounds exist for one of the variables, then -% supply +inf for that variable. -% -% If no upper bounds at all, then UB may be left empty. -% -% Variables may be fixed in value by setting the corresponding -% lower and upper bounds to exactly the same value. -% -% Notes: -% -% If options is supplied, then TolX will apply to the transformed -% variables. All other FMINSEARCH parameters should be unaffected. -% -% Variables which are constrained by both a lower and an upper -% bound will use a sin transformation. Those constrained by -% only a lower or an upper bound will use a quadratic -% transformation, and unconstrained variables will be left alone. -% -% Variables may be fixed by setting their respective bounds equal. -% In this case, the problem will be reduced in size for FMINSEARCH. -% -% The bounds are inclusive inequalities, which admit the -% boundary values themselves, but will not permit ANY function -% evaluations outside the bounds. These constraints are strictly -% followed. -% -% If your problem has an EXCLUSIVE (strict) constraint which will -% not admit evaluation at the bound itself, then you must provide -% a slightly offset bound. An example of this is a function which -% contains the log of one of its parameters. If you constrain the -% variable to have a lower bound of zero, then FMINSEARCHBND may -% try to evaluate the function exactly at zero. -% -% -% Example: -% rosen = @(x) (1-x(1)).^2 + 105*(x(2)-x(1).^2).^2; -% -% fminsearch(rosen,[3 3]) % unconstrained -% ans = -% 1.0000 1.0000 -% -% fminsearchbnd(rosen,[3 3],[2 2],[]) % constrained -% ans = -% 2.0000 4.0000 -% -% See test_main.m for other examples of use. -% -% -% See also: fminsearch, fminspleas -% -% -% Author: John D'Errico -% E-mail: woodchips@rochester.rr.com -% Release: 4 -% Release date: 7/23/06 - -% size checks -xsize = size(x0); -x0 = x0(:); -xLength=length(x0); - -if (nargin<3) || isempty(LB) - LB = repmat(-inf,xLength,1); -else - LB = LB(:); -end -if (nargin<4) || isempty(UB) - UB = repmat(inf,xLength,1); -else - UB = UB(:); -end - -if (xLength~=length(LB)) || (xLength~=length(UB)) - error 'x0 is incompatible in size with either LB or UB.' -end - -% set default options if necessary -if (nargin<5) || isempty(options) - options = optimset('fminsearch'); -end - - -% 0 --> unconstrained variable -% 1 --> lower bound only -% 2 --> upper bound only -% 3 --> dual finite bounds -% 4 --> fixed variable -BoundClass = zeros(xLength,1); -for i=1:xLength - k = isfinite(LB(i)) + 2*isfinite(UB(i)); - BoundClass(i) = k; - if (k==3) && (LB(i)==UB(i)) - BoundClass(i) = 4; - end -end - -% transform starting values into their unconstrained -% surrogates. Check for infeasible starting guesses. -x0u = x0; -k=1; -for i = 1:xLength - switch BoundClass(i) - case 1 - % lower bound only - if x0(i)<=LB(i) - % infeasible starting value. Use bound. - x0u(k) = 0; - else - x0u(k) = sqrt(x0(i) - LB(i)); - end - - % increment k - k=k+1; - case 2 - % upper bound only - if x0(i)>=UB(i) - % infeasible starting value. use bound. - x0u(k) = 0; - else - x0u(k) = sqrt(UB(i) - x0(i)); - end - - % increment k - k=k+1; - case 3 - % lower and upper bounds - if x0(i)<=LB(i) - % infeasible starting value - x0u(k) = -pi/2; - elseif x0(i)>=UB(i) - % infeasible starting value - x0u(k) = pi/2; - else - x0u(k) = 2*(x0(i) - LB(i))/(UB(i)-LB(i)) - 1; - % shift by 2*pi to avoid problems at zero in fminsearch - % otherwise, the initial simplex is vanishingly small - x0u(k) = 2*pi+asin(max(-1,min(1,x0u(k)))); - end - - % increment k - k=k+1; - case 0 - % unconstrained variable. x0u(i) is set. - x0u(k) = x0(i); - - % increment k - k=k+1; - case 4 - % fixed variable. drop it before fminsearch sees it. - % k is not incremented for this variable. - end - -end -% if any of the unknowns were fixed, then we need to shorten -% x0u now. -if k<=xLength - x0u(k:xLength) = []; -end - -% were all the variables fixed? -if isempty(x0u) - % All variables were fixed. quit immediately, setting the - % appropriate parameters, then return. - - % undo the variable transformations into the original space - x = xtransform(x0u); - - % final reshape - x = reshape(x,xsize); - - % stuff fval with the final value - fval = feval(fun,x,varargin); - - % fminsearchbnd was not called - exitflag = 0; - - output.iterations = 0; - output.funcount = 1; - output.algorithm = 'no call (all variables fixed)'; - output.message = 'All variables were held fixed by the applied bounds'; - - % return with no call at all to fminsearch - return -end - - -% Add the wrapper function to the user function right here inline: - intrafun = @(x, varargin) fun(xtransform(x), varargin{:}); - -% Added code: Add wrappers to output function(s) and plot function(s) - you can specify multiple -% output and/or print functions if you use a cell array of function handles. - if ~isempty(options) - % Add a wrapper to the output function(s) - % fetch the output function and put it(them) into a cell array: - OutputFcn = createCellArrayOfFunctions(optimget(options,'OutputFcn',struct('OutputFcn',[]),'fast'),'OutputFcn'); - for ii = 1:length(OutputFcn) - %stop = firstOutputFunction(OutStructure, optimValues, state, varargin) - OutputFcn{ii} = @(x, varargin) OutputFcn{ii}(xtransform(x), varargin{:}); - end - % store the "wrapped" output function back into the options. - options = optimset(options, 'OutputFcn', OutputFcn); - - % Add a wrapper to the plot function(s) - % fetch the plot function and put it(them) into a cell array: - PlotFcn = createCellArrayOfFunctions(optimget(options,'PlotFcns',struct('PlotFcns',[]),'fast'),'PlotFcns'); - for ii = 1:length(PlotFcn) - %stop = firstOutputFunction(OutStructure, optimValues, state, varargin) - PlotFcn{ii} = @(x, varargin) PlotFcn{ii}(xtransform(x), varargin{:}); - end - % store the "wrapped" output function back into the options. - options = optimset(options, 'PlotFcns', PlotFcn); - % Add a wrapper to the print function(s) - end - - - -% now we can call fminsearch, but with our own -% intra-objective function. -[xu,fval,exitflag,output] = fminsearch(intrafun,x0u,options,varargin); -output.algorithm = [output.algorithm ' bounded using fminsearchbnd']; - -% undo the variable transformations into the original space -x = xtransform(xu); - -% final reshape -x = reshape(x,xsize); - - - % ====================================== - % ========= begin NESTED subfunctions ========= - % ====================================== - function xtrans = xtransform(x) - % converts unconstrained variables into their original domains - - xtrans = zeros(xsize); %zeros(xLength, 1); % I changed this to make it same dimension as the x in fminsearch - % was zeros(1, params.xLength) - % k allows some variables to be fixed, thus dropped from the - % optimization. - k=1; - for i = 1:xLength - switch BoundClass(i) - case 1 - % lower bound only - xtrans(i) = LB(i) + x(k).^2; - - k=k+1; - case 2 - % upper bound only - xtrans(i) = UB(i) - x(k).^2; - - k=k+1; - case 3 - % lower and upper bounds - xtrans(i) = (sin(x(k))+1)/2; - xtrans(i) = xtrans(i)*(UB(i) - LB(i)) + LB(i); - % just in case of any floating point problems - xtrans(i) = max(LB(i),min(UB(i),xtrans(i))); - - k=k+1; - case 4 - % fixed variable, bounds are equal, set it at either bound - xtrans(i) = LB(i); - case 0 - % unconstrained variable. - xtrans(i) = x(k); - - k=k+1; - end - end - - end % sub function xtransform end - - - - -end % mainline end - - - - - diff --git a/CanlabCore/HRF_Est_Toolbox2/New_fminsearchbnd/testFMinSearchNew.m b/CanlabCore/HRF_Est_Toolbox2/New_fminsearchbnd/testFMinSearchNew.m deleted file mode 100644 index 74adafe9..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/New_fminsearchbnd/testFMinSearchNew.m +++ /dev/null @@ -1,133 +0,0 @@ -function testFMinSearchNew -% Test function to show two things: -% -% - how the newly-modified fminsearchbnd works -% - how to use output functions and plot functions in fminsearch or fminsearchbnd (or other Matlab -% optimization routines) - heretofore documentation and examples for these have been sparse. -% -% note: use the fminsearchbnd that was modified 2007-Nov-29 by Ken Purchase, which handles plot and -% output functions properly. -% - - - % set up the function that returns a sim structure (the simulation function) - optFn = @(x, varargin) 100*(x(2)-x(1)^2)^2 + (1-x(1))^2; - - x0 = [-1.2 1]; - mins = [-2 0]; %[2 -inf]; - maxs = [Inf Inf]; %[inf 3]; - - % I'm going to pass in a useless extra parameter, just to show that fminsearchbnd and fminsearch - % pass this back thru to your optimization function. Your function could make use if this if - % you watned. - extraParams = 1; - - - % Set up optimization options - you can leave any of these blank and fminsearch will use - % defaults. - searchOptions = struct(... - 'Display','none',... - 'MaxIter','200*numberOfVariables',... - 'MaxFunEvals','200*numberOfVariables',... - 'TolX',1e-6,... - 'TolFun',1e-6, ... - 'FunValCheck','off',... - 'OutputFcn', @firstOutputFunction,... - 'PlotFcns',@firstPlotFunction); - % NOTE: you could add several output or plot functions by incluing a cell array of function - % handles, such as {@firstOutputFunction, @secondOutputFunction} - - - % Run the optimization: - [outX,fval,exitflag,output] = fminsearchbnd(optFn, x0, mins, maxs, searchOptions, extraParams); - - - % Finally, re-run the best case thru the simulation function and display result: - outX - finalValue = optFn(outX, extraParams) - - - - - % Define output and print functions. These functions are nexted WITHIN the overall routine so they - % have access to variables in the above code if needed. - % - - function stop = firstOutputFunction(xOutputfcn, optimValues, state, varargin) - % create an output function for the fMinSearch - % - % inputs: - % 1) xOutputfcn = the current x values - % 2) optimValues - structure having: - % optimValues.iteration = iter; % iteration number - % optimValues.funccount = numf; % number of function eval's so far - % optimValues.fval = f; % value of the function at current iter. - % optimValues.procedure = how; % how is fminsearch current method (expand, contract, etc) - % 3) State = 'iter','init' or 'done' % where we are in the fminsearch algorithm. - % 4) varargin is passed thru fminsearch to the user function and can be anything. - % - - stop = false; - - % NOTE: this makes a bit of a messy display, but shows what you can do with an output - % function. You can get much of the same information using the fminsearch input option - % 'Display', 'iter' - disp(sprintf('Iteration: %d, Evals: %d, Current Min Value: %d', ... - optimValues.iteration, optimValues.funccount, optimValues.fval)); - disp(['Best x so far: [' sprintf('%g ', xOutputfcn) ']']); - - % you could place plotting code here if you didn't want the automatic figure handling of the - % plot functions. - - % you can also modify the value of 'stop' here to true if you want fminseach to terminate - % based on any criteria you'd put here. - end - - - - function stop = firstPlotFunction(xOutputfcn, optimValues, state, varargin) - % create an print function for the fMinSearch - % - % NOTE: The plot functions do their own management of the plot and axes - if you want to - % plot on your own figure or axes, just do the plotting in the output function, and leave - % the plot function blank. - % - % One thing the plot function DOES have it that it installs STOP and PAUSE buttons on the - % plot that allow you to interrupt the optimization to go in and see what's going on, and - % then resume, or stop the iteration and still have it exit normally (and report output - % values, etc). - % - % inputs: - % 1) xOutputfcn = the current x values - % 2) optimValues - structure having: - % optimValues.iteration = iter; % iteration number - % optimValues.funccount = numf; % number of function eval's so far - % optimValues.fval = f; % value of the function at current iter. - % optimValues.procedure = how; % how is fminsearch current method (expand, contract, etc) - % 3) State = 'iter','init' or 'done' % where we are in the fminsearch algorithm. - % 4) varargin is passed thru fminsearch to the user function and can be anything. - % - - stop = false; - - hold on; - % this is fun - it simply plots the optimization variable (inverse figure of merit) as it - % goes along, so you can see it improving, or stop the iterations if it stagnates. - rectangle('Position', ... - [(optimValues.iteration - 0.45) optimValues.fval, 0.9, 0.5*optimValues.fval]); - set(gca, 'YScale', 'log'); - - % when you run this, try pressing the 'stop' or 'pause' buttons on the plot. - - % you can add any code here that you desire. - - end - - - - - -end % end of test code - - - diff --git a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/Anneal_Logit_allstim.m b/CanlabCore/HRF_Est_Toolbox2/Old_stuff/Anneal_Logit_allstim.m deleted file mode 100644 index cc29ef87..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/Anneal_Logit_allstim.m +++ /dev/null @@ -1,152 +0,0 @@ -function [theta,HH,C,P,hrf,fit] = Anneal_Logit_allstim(theta0,t,tc,Run) -% -% [theta,HH,C,P] = Anneal_Logit(theta0,t,tc,Run) -% -% Estimate inverse logit (IL) HRF model using Simulated Annealing -% Creates fitted curve - 3 logistic functions to be summed together - from parameter estimates -% -% INPUT: theta0, t, tc, Run -% Run = stick function -% tc = time course -% t = vector of time points -% theta0 = initial value for the parameter vector -% -% By Martin Lindquist and Tor Wager -% Edited 12/12/06 -% -% further edited by Christian Waugh to include multiple trialtypes - -numstim = length(Run); - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% -% Initial values - -iter = 3000*numstim; % Number of iterations -theta = theta0; % Set initial value for the parameter vector -h0 = cost_allstim(theta0,t,tc,Run); % Calculate cost of initial estimate -%LB = [0.05, 0, 0, 0.05, 2.5, 0.05, 5]; % Previous Lower bounds for parameters -%UB = [5, 5, 2, 2, 7.5, 2, 10]; -LB = [0.05, 1, 0, 0.01, 2.5, 0.05, 3]; % Lower bounds for parameters -UB = [6, 5, 2, 2, 7.5, 3, 10]; % Upper bounds for parameters -LB = repmat(LB, 1, numstim); -UB = repmat(UB, 1, numstim); - -% -% These values may need tweaking depending on the individual situation. -% - -r1= 0.1; % delta parameters -r1b= 0.1; % delta parameters -r2 = .5; % T parameters -r3 = 0.1; % alpha parameters - -t1 = [1 4]; -t1b = [6]; -t2 = [2 5 7]; -t3 = [3]; - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -u = zeros(1,7); -u = repmat(u, 1, numstim); - -HH = zeros(1+iter,7*numstim); % Keep track of theta_i -HH(1,:) = theta0; -P = zeros(1+iter,1); -C = zeros(1+iter,1); % Keep track of the cost function -C(1) = h0; - -cnt = 0; -for i=1:iter, - - T = 200/log(1+i); %Temperature function (may require tweaking) - th = zeros(1,7); - th = repmat(th, 1, numstim); - ind = 0; - %time = zeros(numstim, 1); - - % Choose a new candidate solution theta_{i+1}, based on a random perturbation of the current solution of theta_{i}. - % Check new parameters are within accepted bounds - while ( (sum((LB-th)>0) + sum((th-UB)>0)) > 0) %|| (min(time) == 0), - - % Perturb solution - - for g = 0:(numstim-1) - u(t1+g*7) = normrnd(0,r1,1,2); - u(t1b+g*7) = normrnd(0,r1b,1,1); - u(t2+g*7) = normrnd(0,r2,1,3); - u(t3+g*7) = normrnd(0,r3,1,1); - end - - - % Update solution - th = theta + u; - ind = ind + 1; - - %include below if you want the times of inflection of the IL curves - %to be in a specific order (i.e. T1 < T2 < T3) - %for g = 1:numstim - % if th(g*7-5) < th(g*7-2) && th(g*7-2) < th(g*7) - % time(g) = 1; - % else - % time(g) = 0; - %end - %end - - if(ind > 500), - warning('stuck!'); - th = theta; - %return; - end; - end; - - h = cost_allstim(th,t,tc,Run); - C(i+1) = h; - delta = h - h0; - - % Determine whether to update the parameter vector. - if (unifrnd(0,1) < min(exp(-delta/T),1)), - theta = th; - h0=h; - cnt = cnt+1; - end; - - HH(i+1,:) = theta; - P(i+1) = min(exp(-delta/T),1); - -end; - -%cnt/iter - -[a,b] = min(C); -theta = HH(b,:); -%h - - -% Additional outputs -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Get HRF for final model -if nargout > 4 - hrf = zeros(length(t),numstim); -for g = 1:numstim - hrf(:,g) = Get_Logit(theta(g*7-6:g*7),t); % Calculate HRF estimate (fit, given theta) -end -end - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Convolve HRF and stick function -if nargout > 5 - len = length(Run{1}); - fitt = zeros(len,numstim); -for g = 1:numstim - fits(:,g) = conv(Run{g}, hrf(:,g)); - fitt(:,g) = fits(1:len,g); -end - fit = sum(fitt,2); -end - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - - -return diff --git a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/Det_Logitold.m b/CanlabCore/HRF_Est_Toolbox2/Old_stuff/Det_Logitold.m deleted file mode 100644 index c8b3c971..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/Det_Logitold.m +++ /dev/null @@ -1,128 +0,0 @@ -function [VM, h, fit, e, param] = Det_Logit(V0,t,tc,Run) -% -% [theta,HH,C,P] = Anneal_Logit(theta0,t,tc,Run) -% -% Estimate inverse logit (IL) HRF model -% Creates fitted curve - 3 logistic functions to be summed together - from parameter estimates -% -% INPUT: V0, t, tc, Run -% Run = stick function -% tc = time course -% t = vector of time points -% V0 = initial value for the parameter vector -% -% By Martin Lindquist and Tor Wager -% Edited 10/01/09 -% - -% Find optimal values -options = optimset('MaxFunEvals',10000000,'Maxiter',10000000,'TolX',1e-8,'TolFun',1e-8,'Display','off'); -VM = fminsearch(@msq_logit,V0,options,Run,t,tc); -VM - -% Use optimal values to fit hemodynamic response functions -h = il_hdmf_tw2(t,VM(1:7)); - -%[param] = get_parameters2(h,t); -[param] = get_parameters_logit(h,t,VM(1:7)); - -len = length(Run); -fit = conv(Run, h); -fit = fit(1:len); - -e = tc-fit; - -end - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% -% SUBFUNCTIONS -% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -function m=msq_logit(V,Run, t, tc) - -HR = il_hdmf_tw2(t,V(1:7)); -len = length(Run); -timecourse = conv(Run, HR); -timecourse = timecourse(1:len); - -m=sum((tc-timecourse).^2); - -end - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -function [h,base] = il_hdmf_tw2(t,V) -% inverse logit -- creates fitted curve from parameter estimates -% -% t = vector of time points -% V = parameters - -% 3 logistic functions to be summed together -base = zeros(length(t),3); -A1 = V(1); -T1 = V(2); -d1 = V(3); -A2 = V(4); -T2 = V(5); -A3 = V(6); -T3 = V(7); -d2 = -d1*(ilogit(A1*(1-T1)) - ilogit(A3*(1-T3)))/(ilogit(A2*(1-T2)) + ilogit(A3*(1-T3))); -d3 = abs(d2)-abs(d1); - -base(:,1)= d1*ilogit(A1*(t-T1))'; -base(:,2)= d2*ilogit(A2*(t-T2))'; -base(:,3)= d3*ilogit(A3*(t-T3))'; -h = sum(base,2)'; - -end - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -function [L] = ilogit(t) -L = exp(t)./(1+exp(t)); -end - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% -% function [param] = get_parameters2(hdrf,t) -% % Find model parameters -% % -% % Height - h -% % Time to peak - p (in time units of TR seconds) -% % Width (at half peak) - w -% -% % Calculate Heights and Time to peak: -% -% n = ceil(t(end)*0.8); -% [h,p] = max(abs(hdrf(1:n))); -% h = hdrf(p); -% -% if (h >0) -% v = (hdrf >= h/2); -% else -% v = (hdrf <= h/2); -% end; -% -% [a,b] = min(diff(v)); -% v(b+1:end) = 0; -% w = sum(v); -% -% cnt = p-1; -% g =hdrf(2:end) - hdrf(1:(end-1)); -% while((cnt > 0) & (abs(g(cnt)) <0.001)), -% h = hdrf(cnt); -% p = cnt; -% cnt = cnt-1; -% end; -% -% param = zeros(3,1); -% param(1) = h; -% param(2) = p; -% param(3) = w; -% -% end - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/Fit_Logit_allstim.m b/CanlabCore/HRF_Est_Toolbox2/Old_stuff/Fit_Logit_allstim.m deleted file mode 100644 index 80ff2a53..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/Fit_Logit_allstim.m +++ /dev/null @@ -1 +0,0 @@ -function [VM, hrf, C, fit] = Fit_Logit_allstim(V0,t,tc,Run) % [h1, h2, VM] = Fit_logit(RunA, RunB, tc, t) % Fit inverse logit function model to time course % Initial values numstim = length(Run); len = length(Run{1}); %V0 = [ 0.5 5 1 0.5 25 -1.5 0.5 50]; %V0 = [1 3 1.5 0.5 5 3 15]; LB = [0.05, 1, 0, 0.05, 2.5, 0.05, 4]; % Previous Lower bounds for parameters UB = [5, 5, 2, 2, 7.5, 2, 10]; %LB = [0.05, 1, 0, 0.01, 2.5, 0.05, 3]; % Lower bounds for parameters %UB = [6, 13, 2, 2, 15.5, 3, 18]; % Upper bounds for parameters LB = repmat(LB, 1, numstim); UB = repmat(UB, 1, numstim); % Find optimal values %options = optimset('MaxFunEvals',10000,'Maxiter',10000,'TolX', 1e-4, 'TolFun', 1e-4,'Display','Final'); options = optimset('MaxFunEvals',5000,'Maxiter',5000,'TolX', 1e-4, 'TolFun', 1e-4,'Display','off'); %VM = fminsearch(@msq_timecourse,V02,options,t,tc,RunA,RunB,RunC ); VM = fminsearchbnd(@cost_allstim, V0, LB,UB,options,t,tc,Run); % Use optimal values to fit hemodynamic response functions hrf =zeros(length(t),numstim); fitt = zeros(len,numstim); for g = 1:numstim hrf(:,g) = Get_Logit(VM(g*7-6:g*7),t); % Calculate HRF estimate (fit, given theta) end for g = 1:numstim fits(:,g) = conv(Run{g}, hrf(:,g)); fitt(:,g) = fits(1:len,g); end fit = sum(fitt,2); C = sum((fit-tc).^2); return; \ No newline at end of file diff --git a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/Fit_Logit_allstim_2.m b/CanlabCore/HRF_Est_Toolbox2/Old_stuff/Fit_Logit_allstim_2.m deleted file mode 100644 index 95139b90..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/Fit_Logit_allstim_2.m +++ /dev/null @@ -1 +0,0 @@ -function [VM, hrf, fit] = Fit_Logit_allstim_2(V0,tc,tr, Run, down) % Fit inverse logit function model to time course % Multi-condition case % % Last edited by ML on 02/12/13 % % INPUTS: % % V0 - Initial values for IL-model % tc - time course data % tr - TR % Run - a cell array containing stick functions (one for each condition) % down - downsampling factor % % OUTPUTS: % % VM - final parameter values for IL-model % hrf - estimated HRFs for each condition % fit - estimated time course % Initial values numstim = length(Run); len = length(Run{1}); LB = [0.05, 1, 0, 0.05, 5, 0.05, 8]; % Previous Lower bounds for parameters UB = [2 10, 2, 2, 15, 1, 20]; % LB = [0.05, 1, 0, 0.05, 5, 0.05, 8, 0]; % Previous Lower bounds for parameters % UB = [2 10, 5, 2, 15, 1, 20, 5]; LB = repmat(LB, 1, numstim); UB = repmat(UB, 1, numstim); V0 = repmat(V0,1,numstim); % Find optimal values %options = optimset('MaxFunEvals',10000,'Maxiter',10000,'TolX', 1e-4, 'TolFun', 1e-4,'Display','Final'); options = optimset('MaxFunEvals',100000,'Maxiter',100000,'TolX', 1e-8, 'TolFun', 1e-8,'Display','off'); %VM = fminsearch(@msq_timecourse,V02,options,t,tc,RunA,RunB,RunC ); VM = fminsearchbnd(@cost_allstim_2, V0, LB, UB, options,tr,tc,Run,down); % Use optimal values to fit hemodynamic response functions t=0:(1/down):30; hrf =zeros(length(t),numstim); fitt = zeros(len,numstim); for g = 1:numstim hrf(:,g) = Get_Logit(VM(g*7-6:g*7),t); % Calculate HRF estimate (fit, given theta) end for g = 1:numstim fits(:,g) = conv(Run{g}, hrf(:,g)); fitt(:,g) = fits(1:len,g); end fit = sum(fitt,2); return; \ No newline at end of file diff --git a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/Fit_sFIRold.m b/CanlabCore/HRF_Est_Toolbox2/Old_stuff/Fit_sFIRold.m deleted file mode 100644 index c74c76cf..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/Fit_sFIRold.m +++ /dev/null @@ -1,56 +0,0 @@ -function [hrf, fit, e, param] = Fit_sFIR(tc,TR,Runs,T,mode) -% function [hrf, fit, e, param] = Fit_sFIR(tc,TR,Runs,T,mode) -% -% Fits FIR and smooth FIR model -% -% INPUTS: -% -% tc - time course -% TR - time resolution -% Runs - expermental design -% T - length of estimated HRF -% mode - FIR or smooth FIR -% options: -% 0 - standard FIR -% 1 - smooth FIR -% -% OUTPUTS: -% -% hrf - estimated hemodynamic response function -% fit - estimated time course -% e - residual time course -% param - estimated amplitude, height and width -% -% Created by Martin Lindquist on 10/02/09 - -[DX] = tor_make_deconv_mtx3(Runs,T,1); -DX2 = DX(:,1:T); -num = T; - -if mode == 1 - - C=(1:num)'*(ones(1,num)); - h = sqrt(1/(7/TR)); % 7 seconds smoothing - ref. Goutte - - v = 0.1; - sig = 1; - - R = v*exp(-h/2*(C-C').^2); - RI = inv(R); - - b = inv(DX2'*DX2+sig^2*RI)*DX2'*tc; - fit = DX2*b; - e = tc - DX2*b; - -elseif mode == 0 - - b = pinv(DX)*tc; - fit = DX*b; - e = tc - DX*b; - -end - -hrf = b; -param = get_parameters2(b,T); - -end \ No newline at end of file diff --git a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/Get_parameters_Logit.m b/CanlabCore/HRF_Est_Toolbox2/Old_stuff/Get_parameters_Logit.m deleted file mode 100644 index f1d9799e..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/Get_parameters_Logit.m +++ /dev/null @@ -1,77 +0,0 @@ -function [param] = get_parameters_logit(hdrf,t,VM) -% -% [param] = get_parameters_logit(hdrf,t,VM) -% -% Estimate Height, time-to-peak and Width for the inverse logit model -% -% INPUT: hdrf,t,VM -% hdrf - HRF estimated using IL model -% t - vector of time points -% VM - IL model parameters -% -% OUTPUT: param =[h,p,w] -% Height - h -% Time to peak - p (in time units of TR seconds) -% Width (at half peak) - w -% -% By Martin Lindquist and Tor Wager -% Edited 12/12/06 -% - -% Estimate Heights and Time to peak: - -[dL, dL2, d1, d2] = dilogit_dt2(t,VM); % Calculate first and second derivativeof IL model -g = (abs(diff(sign(dL))) >= 1); -cnt = max(1,ceil(VM(2))); %Peak has to be further along then T1 - -p =-1; -while(cnt < max(t)), - if((g(cnt) == 1) & (dL2(cnt) <= 0)), - p = cnt; - h = hdrf(p); - cnt = max(t) +1; - end; - cnt = cnt+1; -end; - -if (p == -1), - [h,p] = max(hdrf); -end; - - -% Interpolate to get finer estimation of p -if (p>1 & p= h/2); -[a,b2] = min(diff(v)); -v(b2+1:end) = 0; -[a,b1] = max(v); - -% Interpolate to get finer estimation of v - -if (p>1 && p= h/2)); - tp = b2 + (0:0.01:1); - htmp = Get_Logit(VM,tp); - delta2 = sum((htmp >= h/2)); - w = sum(v) + delta1/100 + delta2/100; -else - w = sum(v)+1; -end; - -% Set output vector param -param = zeros(3,1); -param(1) = h; -param(2) = p; -param(3) = w; - -return; diff --git a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/Get_parameters_Logit2_2hrfs.m b/CanlabCore/HRF_Est_Toolbox2/Old_stuff/Get_parameters_Logit2_2hrfs.m deleted file mode 100644 index 4ddb5c40..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/Get_parameters_Logit2_2hrfs.m +++ /dev/null @@ -1,176 +0,0 @@ -function [param, hdrf2] = Get_parameters_Logit2_2hrfs(hdrf,t,VM,t1t2) -% -% [param] = get_parameters_logit(hdrf,t,VM) -% -% Different from version 1 in that it only calculates increases as 'peaks' -% Estimate Height, time-to-peak and Width for the inverse logit model -% -% INPUT: hdrf,t,VM (all with two rows for each HRF of interest) -% hdrf - HRF estimated using IL model -% t - vector of time points -% VM - IL model parameters -% t1t2 - number of time points separating the onset of the first and second -% hrfs - -% OUTPUT: param =[h,p,w] -% Height - h -% Time to peak - p (in time units of TR seconds) -% Width (at half peak) - w -% -% By Martin Lindquist and Tor Wager -% Edited 12/12/06 -% -% In addition, it'll calculate the width from two HRFs put together in case -% your design is such that two events are dependent on each other (i.e. -% cue->stimulus) -% - -% added by Christian Waugh - -% Estimate Heights and Time to peak for first HRF: - -[dL, dL2, d1, d2] = dilogit_dt2(t,VM(1, :)); % Calculate first and second derivativeof IL model -g = (abs(diff(sign(dL))) >= 1); -%cnt = max(1,ceil(VM(1, :)(2))); %Peak has to be further along then T1 -cnt = max(1,ceil(min(min(VM(1,2),VM(1,5)),VM(1,7)))); - -p =-1; - -while(cnt < max(t)), - if((g(cnt) == 1) && (dL2(cnt) <= 0)), - p = cnt; - h = hdrf(p,1); - cnt = max(t) +1; - end; - cnt = cnt+1; -end; - - -if (p == -1) - [h,p] = max(hdrf(:,1)); -end; - - -% Interpolate to get finer estimation of p -if (p>1 && p= h/2); -[a,b2] = min(diff([v;0])); -if b2 < length(v) -v(b2+1:end) = 0; -end -[a,b1] = max(v); - -% Interpolate to get finer estimation of v - -if (p>1 && p= h/2)); - tp = b2 + (0:0.01:1); - - htmp = Get_Logit(VM(1, :),tp); - delta2 = sum((htmp >= h/2)); - w = sum(v) + delta1/100 + delta2/100; -else - w = sum(v)+1; -end; - -% Set output vector param -param = zeros(7,1); -param(1) = h; -param(2) = p; -param(3) = w; - - -% Estimate Heights and Time to peak for second HRF: - -[dLb, dL2b, d1b, d2b] = dilogit_dt2(t,VM(2, :)); % Calculate first and second derivativeof IL model -gb = (abs(diff(sign(dLb))) >= 1); -%cnt = max(1,ceil(VM(1, :)(2))); %Peak has to be further along then T1 -cntb = max(1,ceil(min(min(VM(2,2),VM(2,5)),VM(2,7)))); - -pb =-1; -hb = 0; - - -while(cntb < max(t)), - if((gb(cntb) == 1) && (dL2b(cntb) <= 0)), - pb = cntb; - hb = hdrf(pb,2); - cntb = max(t) +1; - end; - cntb = cntb+1; -end; - - -if (pb == -1) - [hb,pb] = max(hdrf(:,2)); -end; - - -% Interpolate to get finer estimation of p -if (pb>1 & pb= hb/2); - -[ab,b2b] = min(diff([vb;0])); -if b2b < length(vb) -vb(b2b+1:end) = 0; -end -[ab,b1b] = max(vb); - -% Interpolate to get finer estimation of v - -if (pb>1 & pb= hb/2)); - tpb = b2b + (0:0.01:1); - - htmpb = Get_Logit(VM(2, :),tpb); - delta2 = sum((htmpb >= hb/2)); - wb = sum(vb) + delta1/100 + delta2/100; -else, - wb = sum(vb)+1; -end; - -% Set output vector param -param(4) = hb; -param(5) = pb; -param(6) = wb; - - -% Combine hrfs 1 and 2 to get an overall width estimate - -hdrf2 = [hdrf(:,1); repmat(0,t1t2,1)] + [repmat(0,t1t2,1); hdrf(:,2)]; - - vc = (hdrf2 >= h/2); - -[ac,b2c] = min(diff([vc;0])); -if b2c < length(vc) -vc(b2c+1:end) = 0; -end -[ac,b1c] = max(vc); - - -wc = sum(vc)+1; - -param(7) = wc; - -return; diff --git a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Anneal_Logit.m b/CanlabCore/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Anneal_Logit.m deleted file mode 100644 index f45f2316..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Anneal_Logit.m +++ /dev/null @@ -1,126 +0,0 @@ -function [theta,HH,C,P,hrf,fit,e,param] = Anneal_Logit(theta0,t,tc,Run) -% -% [theta,HH,C,P] = Anneal_Logit(theta0,t,tc,Run) -% -% Estimate inverse logit (IL) HRF model using Simulated Annealing -% Creates fitted curve - 3 logistic functions to be summed together - from parameter estimates -% -% INPUT: theta0, t, tc, Run -% Run = stick function -% tc = time course -% t = vector of time points -% theta0 = initial value for the parameter vector -% -% By Martin Lindquist and Tor Wager -% Edited 12/12/06 -% - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% -% Initial values - -iter = 15000; % Number of iterations -theta = theta0; % Set initial value for the parameter vector -h0 = cost(theta0,t,tc,Run); % Calculate cost of initial estimate -LB = [0.05, 1, 0, 0.05, 5, 0, 10]; % Lower bounds for parameters -UB = [10, 15, 5, 10, 15, 5, 30]; % Upper bounds for parameters - -% -% These values may need tweaking depending on the individual situation. -% - -r1= 0.001; % A parameters -r1b= 0.001; % A parameters -r2 = 0.05; % T parameters -r3 = 0.001; % delta parameters - -t1 = [1 4]; -t1b = [6]; -t2 = [2 5 7]; -t3 = [3]; - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -u = zeros(1,7); - -HH = zeros(1+iter,7); % Keep track of theta_i -HH(1,:) = theta0; -P = zeros(1+iter,1); -C = zeros(1+iter,1); % Keep track of the cost function -C(1) = h0; - -cnt = 0; -for i=1:iter, - - T = 100/log(1+i); %Temperature function (may require tweaking) - th = zeros(1,7); - ind = 0; - - % Choose a new candidate solution theta_{i+1}, based on a random perturbation of the current solution of theta_{i}. - % Check new parameters are within accepted bounds - while ( (sum((LB-th)>0) + sum((th-UB)>0)) > 0), - - % Perturb solution - - u(t1) = normrnd(0,r1,1,2); - u(t1b) = normrnd(0,r1b,1,1); - u(t2) = normrnd(0,r2,1,3); - u(t3) = normrnd(0,r3,1,1); - - % Update solution - th = theta + u; - ind = ind + 1; - - if(ind > 500), - warning('stuck!'); - return; - end; - end; - - h = cost(th,t,tc,Run); - C(i+1) = h; - delta = h - h0; - - % Determine whether to update the parameter vector. - if (unifrnd(0,1) < min(exp(-delta/T),1)), - theta = th; - h0=h; - cnt = cnt+1; - end; - - HH(i+1,:) = theta; - P(i+1) = min(exp(-delta/T),1); - -end; - -%cnt/iter - -[a,b] = min(C); -theta = HH(b,:); -%h - - -% Additional outputs -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Get HRF for final model -if nargout > 4 - hrf = Get_Logit(theta(1:7),t); % Calculate HRF estimate (fit, given theta) -end - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Convolve HRF and stick function -if nargout > 5 - len = length(Run); - fit = conv(Run, hrf); - fit = fit(1:len); - e = tc - fit; -end - -if nargout > 7 - [param] = get_parameters_logit(hrf,t,theta); -end - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - - -return diff --git a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Det_Logit.m b/CanlabCore/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Det_Logit.m deleted file mode 100644 index 83cfe423..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Det_Logit.m +++ /dev/null @@ -1,116 +0,0 @@ -function [VM, hrf, fit, e, param] = Det_Logit(V0,t,tc,Run) -% -% [VM, h, fit, e, param] = Det_Logit_allstim(V0,t,tc,Run) -% -% Estimate inverse logit (IL) HRF model -% Creates fitted curve - 3 logistic functions to be summed together - from parameter estimates -% -% INPUT: V0, t, tc, Run -% Run = stick function -% tc = time course -% t = vector of time points -% V0 = initial value for the parameter vector -% -% By Martin Lindquist, Christian Waugh and Tor Wager -% Created by Martin Lindquist on 10/02/09 -% Last edited: 05/26/10 (ML) - - -numstim = length(Run); -len = length(Run{1}); - -% LB = [0.05, 1, 0, 0.05, 4, 0, 10]; % Lower bounds for parameters -% UB = [10, 15, 10, 10, 15, 5, 50]; % Upper bounds for parameters -% LB = repmat(LB, 1, numstim); -% UB = repmat(UB, 1, numstim); - -% Remove intercept - -b0 = pinv(ones(length(tc),1))*tc; -tc = tc - b0; - -% Find optimal values - -options = optimset('MaxFunEvals',10000000,'Maxiter',10000000,'TolX',1e-8,'TolFun',1e-8,'Display','off'); - -%VM = fminsearchbnd(@cost_allstim, V0, LB,UB,options,t,tc,Run); -VM = fminsearch(@msq_logit,V0,options,Run,t,tc); - -% Use optimal values to fit hemodynamic response functions -hrf =zeros(length(t),numstim); -fitt = zeros(len,numstim); -param = zeros(3,numstim); - -for g = 1:numstim - hrf(:,g) = il_hdmf_tw2(t,VM(((g-1)*7+1):(g*7))); % Calculate HRF estimate (fit, given theta) - param(:,g) = get_parameters2(hrf(:,g),t(end)); - fits(:,g) = conv(Run{g}, hrf(:,g)); - fitt(:,g) = fits(1:len,g); -end - -fit = sum(fitt,2); -e = tc-fit; -fit = fit + b0; - -end - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% -% SUBFUNCTIONS -% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -function m=msq_logit(V,Run, t, tc) - -numstim = length(Run); -len = length(Run{1}); -h = zeros(length(t),numstim); -yhatt =zeros(len,numstim); - -for k = 1:numstim - h(:,k) = il_hdmf_tw2(t,V(((k-1)*7+1):(k*7))); % Get IL model corresponding to parameters V - yhat(:,k) = conv(Run{k}, h(:,k)); % Convolve IL model with stick function - yhatt(:,k) = yhat(1:len,k); -end - -yhat2 = sum(yhatt,2); %Sum models together to get overall estimate - -m = sum((tc-yhat2).^2); % Calculate cost function - -end - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -function [h,base] = il_hdmf_tw2(t,V) -% inverse logit -- creates fitted curve from parameter estimates -% -% t = vector of time points -% V = parameters - -% 3 logistic functions to be summed together -base = zeros(length(t),3); -A1 = V(1); -T1 = V(2); -d1 = V(3); -A2 = V(4); -T2 = V(5); -A3 = V(6); -T3 = V(7); -d2 = -d1*(ilogit(A1*(1-T1)) - ilogit(A3*(1-T3)))/(ilogit(A2*(1-T2)) + ilogit(A3*(1-T3))); -d3 = abs(d2)-abs(d1); - -base(:,1)= d1*ilogit(A1*(t-T1))'; -base(:,2)= d2*ilogit(A2*(t-T2))'; -base(:,3)= d3*ilogit(A3*(t-T3))'; -h = sum(base,2)'; - -end - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -function [L] = ilogit(t) -L = exp(t)./(1+exp(t)); -end - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Example.m b/CanlabCore/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Example.m deleted file mode 100644 index 03497e65..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Example.m +++ /dev/null @@ -1,176 +0,0 @@ -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% -% Example code for estimating the HRF using the Inverse-Logit Model, a -% Finte Impluse Response Model and the Canonical HRF with 2 derivatives. -% Also the code illustrates our code for detecting model misspecification. -% -% By Martin Lindquist and Tor Wager -% Created: 10/02/09 -% Last edited: 05/26/10 -% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% -% Load time course -% - -mypath = which('ilogit'); -if isempty(mypath), error('Cannot find directory with ilogit.m and other functions. Not on path?'); end -[mydir] = fileparts(mypath) - -load(fullfile(mydir,'timecourse')) - -tc = (tc- mean(tc))/std(tc); -len = length(tc); -figure; subplot(3,1,1); han = plot(tc); -title('Sample time course'); drawnow - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% -% Settings -% - -TR = 0.5; -T = round(30/TR); -t = 1:T; % samples at which to get Logit HRF Estimate -FWHM = 4; % FWHM for residual scan -pval = 0.01; -df = 600; -alpha = 0.001; - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% -% Create stick function (sample event onsets) -% Variable R contains onset times -% Variable Run contains stick (a.k.a. delta or indicator) function - -R = [3, 21, 56, 65, 109, 126, 163, 171, 216, 232, 269, 282, 323, 341, 376, 385, 429, 446, 483, 491, 536, 552, 589, 602]; -Runs = zeros(640,1); -for i=1:length(R), Runs(R(i)) = 1; end; - -Run = []; -Run{1}=Runs; - - -try - hold on; - hh = plot_onsets(R,'k',-3,1); - drawnow -catch - disp('Couldn''t find function to add onset sticks to plot. Skipping.') -end - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Fit HRF using IL-function - -% Choose mode (deterministic/stochastic) - -mode = 0; % 0 - deterministic aproach - % 1 - simulated annealing approach - % Please note that when using simulated annealing approach you - % may need to perform some tuning before use. - - -[h1, fit1, e1, param] = Fit_Logit(tc,Run,t,mode); -[pv sres sres_ns1] = ResidScan(e1, FWHM); -[PowLoss1] = PowerLoss(e1, fit1, (len-7) , tc, TR, Run, alpha); - -hold on; han(2) = plot(fit1,'r'); - -disp('Summary: IL_function'); - -disp('Amplitude:'); disp(param(1)); -disp('Time-to-peak:'); disp(param(2)); -disp('Width:'); disp(param(3)); - -disp('MSE:'); disp((1/(len-1)*sum(e1.^2))); -disp('Mis-modeling:'); disp(pv); -disp('Power Loss:'); disp(PowLoss1); - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Fit HRF using FIR-model - -% Choose mode (FIR/sFIR) - -mode = 1; % 0 - FIR - % 1 - smooth FIR - -[h2, fit2, e2, param] = Fit_sFIR(tc,TR,Run,T,mode); -[pv sres sres_ns2] = ResidScan(e2, FWHM); -[PowLoss2] = PowerLoss(e2, fit2, (len-T) , tc, TR, Run, alpha); - -hold on; han(3) = plot(fit2,'g'); - -disp('Summary: FIR'); - -disp('Amplitude'); disp(param(1)); -disp('Time-to-peak'); disp(param(2)); -disp('Width'); disp(param(3)); - -disp('MSE:'); disp((1/(len-1)*sum(e2.^2))); -disp('Mis-modeling'); disp(pv); -disp('Power Loss:'); disp(PowLoss2); - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Fit HRF using Canonical HRF + 2 derivatives - -p=1; - -[h3, fit3, e3, param, info] = Fit_Canonical_HRF(tc,TR,Run,T,p); -[pv sres sres_ns3] = ResidScan(e3, FWHM); -[PowLoss3] = PowerLoss(e3, fit3, (len-p) , tc, TR, Run, alpha); - -hold on; han(4) = plot(fit3,'m'); - -legend(han,{'Data' 'IL' 'sFIR' 'DD'}) - - -disp('Summary: Canonical + 2 derivatives'); - -disp('Amplitude'); disp(param(1)); -disp('Time-to-peak'); disp(param(2)); -disp('Width'); disp(param(3)); - -disp('MSE:'); disp((1/(len-1)*sum(e3.^2))); -disp('Mis-modeling'); disp(pv); -disp('Power Loss:'); disp(PowLoss3); - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%figure; - - -subplot(3,2,5); -han2 = plot(h1,'r'); -hold on; han2(2) = plot(h2,'g'); -hold on; han2(3) = plot(h3,'m'); -legend(han2,{'IL' 'sFIR' 'DD'}) -title('Estimated HRF'); - - -subplot(3,1,2); hold on; -hh = plot_onsets(R,'k',-3,1); -drawnow - -han3 = plot(sres_ns1,'r'); -hold on; han3(2) = plot(sres_ns2,'g'); -hold on; han3(3) = plot(sres_ns3,'m'); -hold on; plot((1:len),zeros(len,1),'--k'); -legend(han3,{'IL' 'sFIR' 'DD'}) -title('Mis-modeling (time course)'); - - -subplot(3,2,6); hold on; - -[s1] = Fit_sFIR(sres_ns1,TR,Run,T,0); -[s2] = Fit_sFIR(sres_ns2,TR,Run,T,0); -[s3] = Fit_sFIR(sres_ns3,TR,Run,T,0); - -han4 = plot(s1(1:T),'r'); -hold on; han4(2) = plot(s2(1:T),'g'); -hold on; han4(3) = plot(s3(1:T),'m'); -hold on; plot((1:T),zeros(T,1),'--k'); -legend(han4,{'IL' 'sFIR' 'DD'}) -title('Mis-modeling (HRF)'); diff --git a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Example2.m b/CanlabCore/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Example2.m deleted file mode 100644 index 26ac6c06..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Example2.m +++ /dev/null @@ -1,196 +0,0 @@ -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% -% Example code for estimating the HRF using the Inverse-Logit Model, a -% Finte Impluse Response Model and the Canonical HRF with 2 derivatives. -% Also the code illustrates our code for detecting model misspecification. -% -% This example illustrates the multi-stimulus version -% -% By Martin Lindquist -% Created: 05/26/10 -% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% -% Load time course -% - -mypath = which('ilogit'); -if isempty(mypath), error('Cannot find directory with ilogit.m and other functions. Not on path?'); end -[mydir] = fileparts(mypath) - - -h = spm_hrf(1); -h = h./max(h); - -RunA = zeros(60,10); -RunA(1,:) = 1; -RunA = reshape(RunA,600,1); -RunB = zeros(60,10); -RunB(31,:) = 1; -RunB = reshape(RunB,600,1); - -tc = 2*conv(RunA,h) + conv(RunB,h); -tc = tc(1:600); -tc = tc+normrnd(0,1,600,1); - -Run =[]; -Run{1} = RunA; -Run{2} = RunB; - -len = length(tc); - -figure; subplot(3,1,1); han = plot(tc); -title('Sample time course'); drawnow - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% -% Settings -% - -TR = 1; -T = round(30/TR); -t = 1:T; % samples at which to get Logit HRF Estimate -FWHM = 4; % FWHM for residual scan -pval = 0.01; -df = 600; -alpha = 0.001; - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% -% Create stick function (sample event onsets) -% Variable R contains onset times -% Variable Run contains stick (a.k.a. delta or indicator) function - -RA = [1 61 121 181 241 301 361 421 481 541]; -RB = [31 91 151 211 271 331 391 451 511 571]; - - -try - hold on; - hh = plot_onsets(RA,'k',-3,1); - hh = plot_onsets(RB,'r',-3,0.5); - drawnow -catch - disp('Couldn''t find function to add onset sticks to plot. Skipping.') -end - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Fit HRF using IL-function - -% Choose mode (deterministic/stochastic) - -mode = 0; % 0 - deterministic aproach - % 1 - simulated annealing approach - % Please note that when using simulated annealing approach you - % may need to perform some tuning before use. - - - - -[h1, fit1, e1, param] = Fit_Logit(tc,Run,t,mode); -[pv sres sres_ns1] = ResidScan(e1, FWHM); -[PowLoss1] = PowerLoss(e1, fit1, (len-7) , tc, TR, Run, alpha); - -hold on; han(2) = plot(fit1,'r'); - -disp('Summary: IL_function'); - -disp('HRF - Event A'); -disp('Amplitude:'); disp(param(1,1)); -disp('Time-to-peak:'); disp(param(2,1)); -disp('Width:'); disp(param(3,1)); - -disp('HRF - Event B'); -disp('Amplitude:'); disp(param(1,2)); -disp('Time-to-peak:'); disp(param(2,2)); -disp('Width:'); disp(param(3,2)); - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Fit HRF using FIR-model - -% Choose mode (FIR/sFIR) - -mode = 1; % 0 - FIR - % 1 - smooth FIR - -[h2, fit2, e2, param] = Fit_sFIR(tc,TR,Run,T,mode); -[pv sres sres_ns2] = ResidScan(e2, FWHM); -[PowLoss2] = PowerLoss(e2, fit2, (len-T) , tc, TR, Run, alpha); - -hold on; han(3) = plot(fit2,'g'); - -disp('Summary: FIR'); - -disp('HRF - Event A'); -disp('Amplitude:'); disp(param(1,1)); -disp('Time-to-peak:'); disp(param(2,1)); -disp('Width:'); disp(param(3,1)); - -disp('HRF - Event B'); -disp('Amplitude:'); disp(param(1,2)); -disp('Time-to-peak:'); disp(param(2,2)); -disp('Width:'); disp(param(3,2)); - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Fit HRF using Canonical HRF + 2 derivatives - -p=1; - -[h3, fit3, e3, param, info] = Fit_Canonical_HRF(tc,TR,Run,T,p); -[pv sres sres_ns3] = ResidScan(e3, FWHM); -[PowLoss3] = PowerLoss(e3, fit3, (len-p) , tc, TR, Run, alpha); - -hold on; han(4) = plot(fit3,'m'); - -legend(han,{'Data' 'IL' 'sFIR' 'DD'}) - - -disp('Summary: Canonical + 2 derivatives'); - -disp('HRF - Event A'); -disp('Amplitude:'); disp(param(1,1)); -disp('Time-to-peak:'); disp(param(2,1)); -disp('Width:'); disp(param(3,1)); - -disp('HRF - Event B'); -disp('Amplitude:'); disp(param(1,2)); -disp('Time-to-peak:'); disp(param(2,2)); -disp('Width:'); disp(param(3,2)); - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%figure; - - -subplot(3,2,5); -han2 = plot(h1(:,1),'r'); -hold on; han2(2) = plot(h2(:,1),'g'); -hold on; han2(3) = plot(h3(:,1),'m'); -legend(han2,{'IL' 'sFIR' 'DD'}) -title('Estimated HRF - Event A'); - - -subplot(3,1,2); hold on; -hh = plot_onsets(RA,'k',-3,1); -hh = plot_onsets(RB,'r',-3,0.5); -drawnow - -han3 = plot(sres_ns1,'r'); -hold on; han3(2) = plot(sres_ns2,'g'); -hold on; han3(3) = plot(sres_ns3,'m'); -hold on; plot((1:len),zeros(len,1),'--k'); -legend(han3,{'IL' 'sFIR' 'DD'}) -title('Mis-modeling (time course)'); - - -subplot(3,2,6); hold on; - -han4 = plot(h2(:,1),'r'); -hold on; han4(2) = plot(h2(:,2),'g'); -hold on; han4(3) = plot(h3(:,2),'m'); -legend(han4,{'IL' 'sFIR' 'DD'}) -title('Estimated HRF - Event A'); diff --git a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Example_old.m b/CanlabCore/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Example_old.m deleted file mode 100644 index 1ed0a257..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Example_old.m +++ /dev/null @@ -1,207 +0,0 @@ -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% -% Example code for estimating the HRF using the Inverse-Logit Model, a -% Finte Impluse Response Model and the Canonical HRF with 2 derivatives. -% Also the code illustrates our code for detecting model misspecification. -% -% By Martin Lindquist and Tor Wager -% Edited 05/17/13 -% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% -% Load time course -% - -mypath = which('ilogit'); -if isempty(mypath), error('Cannot find directory with ilogit.m and other functions. Not on path?'); end -[mydir] = fileparts(mypath) - -load(fullfile(mydir,'timecourse')) - -tc = (tc- mean(tc))/std(tc); -len = length(tc); - - -%% Or: create your own -[xBF] = spm_get_bf(struct('dt', .5, 'name', 'hrf (with time and dispersion derivatives)', 'length', 32)); -clear Xtrue -for i = 1:1, xx = conv(xBF.bf(:,i), [1 1 1 1 1 1 ]'); - Xtrue(:, i) = xx(1:66); -end -for i = 2:3, xx = conv(xBF.bf(:,i), [1 1]'); - Xtrue(:, i) = xx(1:66); -end -hrf = Xtrue * [1 .3 .2]'; -xsecs = 0:.5:32; -hrf = hrf(1:length(xsecs)); -hrf = hrf ./ max(hrf); -figure; plot(xsecs, hrf, 'k') -%hrf = hrf(1:4:end); % downsample to TR, if TR is > 0.5 - - -R = randperm(640); R = sort(R(1:36)); -Run = zeros(640,1); -for i=1:length(R), Run(R(i)) = 1; end; -true_sig = conv(Run, hrf); -true_sig = true_sig(1:640); - -tc_noise = noise_arp(640, [.7 .2]); -tc = true_sig + 0 * tc_noise; -%figure; plot(tc); - - -%% - -create_figure; subplot(3,1,1); han = plot(tc); -title('Sample time course'); drawnow - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% -% Settings -% - -TR = 0.5; -% T = round(30/TR); -% t = 1:T; % samples at which to get Logit HRF Estimate -T = 30; -FWHM = 4; % FWHM for residual scan -pval = 0.01; -df = 600; -alpha = 0.001; - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% -% Create stick function (sample event onsets) -% Variable R contains onset times -% Variable Run contains stick (a.k.a. delta or indicator) function - -R = [3, 21, 56, 65, 109, 126, 163, 171, 216, 232, 269, 282, 323, 341, 376, 385, 429, 446, 483, 491, 536, 552, 589, 602]; -Run = zeros(640,1); -for i=1:length(R), Run(R(i)) = 1; end; -Runc = {}; -Runc{1} = Run; - -try - hold on; - hh = plot_onsets(R,'k',-3,1); - drawnow -catch - disp('Couldn''t find function to add onset sticks to plot. Skipping.') -end - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Fit HRF using IL-function - -% Choose mode (deterministic/stochastic) - -mode = 1; % 0 - deterministic aproach - % 1 - simulated annealing approach - % Please note that when using simulated annealing approach you - % may need to perform some tuning before use. - -%[h1, fit1, e1, param] = Fit_Logit_allstim(tc,Run,t,mode); -V0 = [ 1 6 1 0.5 10 1 15]; -[VM, h1, fit1, e1, param] = Fit_Logit_allstim(V0,tc,TR, Runc, T); -[pv sres sres_ns1] = ResidScan(e1, FWHM); -[PowLoss1] = PowerLoss(e1, fit1, (len-7) , tc, TR, Runc, alpha); - -hold on; han(2) = plot(fit1,'r'); - -disp('Summary: IL_function'); - -disp('Amplitude:'); disp(param(1)); -disp('Time-to-peak:'); disp(param(2)); -disp('Width:'); disp(param(3)); - -disp('MSE:'); disp((1/(len-1)*sum(e1.^2))); -disp('Mis-modeling:'); disp(pv); -disp('Power Loss:'); disp(PowLoss1); - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Fit HRF using FIR-model - -% Choose mode (FIR/sFIR) - -mode = 1; % 0 - FIR - % 1 - smooth FIR - -[h2, fit2, e2, param] = Fit_sFIR(tc,TR,Runc,T,mode); -[pv sres sres_ns2] = ResidScan(e2, FWHM); -[PowLoss2] = PowerLoss(e2, fit2, (len-T) , tc, TR, Runc, alpha); - -hold on; han(3) = plot(fit2,'g'); - -disp('Summary: FIR'); - -disp('Amplitude'); disp(param(1)); -disp('Time-to-peak'); disp(param(2)); -disp('Width'); disp(param(3)); - -disp('MSE:'); disp((1/(len-1)*sum(e2.^2))); -disp('Mis-modeling'); disp(pv); -disp('Power Loss:'); disp(PowLoss2); - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Fit HRF using Canonical HRF + 2 derivatives - -p=1; - -[h3, fit3, e3, param, info] = Fit_Canonical_HRF(tc,TR,Runc,T,p); -[pv sres sres_ns3] = ResidScan(e3, FWHM); -[PowLoss3] = PowerLoss(e3, fit3, (len-p) , tc, TR, Runc, alpha); - -hold on; han(4) = plot(fit3,'m'); - -legend(han,{'Data' 'IL' 'sFIR' 'DD'}) - - -disp('Summary: Canonical + 2 derivatives'); - -disp('Amplitude'); disp(param(1)); -disp('Time-to-peak'); disp(param(2)); -disp('Width'); disp(param(3)); - -disp('MSE:'); disp((1/(len-1)*sum(e3.^2))); -disp('Mis-modeling'); disp(pv); -disp('Power Loss:'); disp(PowLoss3); - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%figure; - - -subplot(3,2,5); -han2 = plot(h1,'r'); -hold on; han2(2) = plot(h2,'g'); -hold on; han2(3) = plot(h3,'m'); -legend(han2,{'IL' 'sFIR' 'DD'}) -title('Estimated HRF'); - - -subplot(3,1,2); hold on; -hh = plot_onsets(R,'k',-3,1); -drawnow - -han3 = plot(sres_ns1,'r'); -hold on; han3(2) = plot(sres_ns2,'g'); -hold on; han3(3) = plot(sres_ns3,'m'); -hold on; plot((1:len),zeros(len,1),'--k'); -legend(han3,{'IL' 'sFIR' 'DD'}) -title('Mis-modeling (time course)'); - - -subplot(3,2,6); hold on; - -[s1] = Fit_sFIR(sres_ns1,TR,Runc,T,0); -[s2] = Fit_sFIR(sres_ns2,TR,Runc,T,0); -[s3] = Fit_sFIR(sres_ns3,TR,Runc,T,0); - -han4 = plot(s1(1:T),'r'); -hold on; han4(2) = plot(s2(1:T),'g'); -hold on; han4(3) = plot(s3(1:T),'m'); -hold on; plot((1:T),zeros(T,1),'--k'); -legend(han4,{'IL' 'sFIR' 'DD'}) -title('Mis-modeling (HRF)'); diff --git a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Fit_Canonical_HRF.m b/CanlabCore/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Fit_Canonical_HRF.m deleted file mode 100644 index 078b3951..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Fit_Canonical_HRF.m +++ /dev/null @@ -1,119 +0,0 @@ -function [hrf, fit, e, param, info] = Fit_Canonical_HRF(tc,TR,Run,T,p) -% function [hrf, fit, e, param, info] = Fit_Canonical_HRF(tc,TR,Runs,T,p) -% -% Fits GLM using canonical hrf (with option of using time and dispersion derivatives)'; -% -% INPUTS: -% -% tc - time course -% TR - time resolution -% Runs - expermental design -% T - length of estimated HRF -% p - Model type -% -% Options: p=0 - only canonical HRF -% p=1 - canonical + temporal derivative -% p=2 - canonical + time and dispersion derivative -% -% OUTPUTS: -% -% hrf - estimated hemodynamic response function -% fit - estimated time course -% e - residual time course -% param - estimated amplitude, height and width -% info - struct containing design matrices, beta values etc -% -% Created by Martin Lindquist on 10/02/09 -% Last edited: 05/26/10 (ML) - -d = length(Run); -len = length(Run{1}); - -X = zeros(len,p*d); -param = zeros(3,d); - -[h, dh, dh2] = CanonicalBasisSet(TR); - -for i=1:d, - v = conv(Run{i},h); - X(:,(i-1)*p+1) = v(1:len); - - if (p>1) - v = conv(Run{i},dh); - X(:,(i-1)*p+2) = v(1:len); - end - - if (p>2) - v = conv(Run{i},dh2); - X(:,(i-1)*p+3) = v(1:len); - end -end - -X = [(zeros(len,1)+1) X]; - -b = pinv(X)*tc; -e = tc-X*b; -fit = X*b; - -b = reshape(b(2:end),p,d)'; -bc = zeros(d,1); - -for i=1:d, - if (p == 1) - bc(i) = b(i,1); - H = h; - elseif (p==2) - bc(i) = sign(b(i,1))*sqrt((b(i,1))^2 + (b(i,2))^2); - H = [h dh]; - elseif (p>2) - bc(i) = sign(b(i,1))*sqrt((b(i,1))^2 + (b(i,2))^2 + (b(i,3))^2); - H = [h dh dh2]; - end - -end - -hrf = H*b'; - -for i=1:d, - param(:,i) = get_parameters2(hrf(:,i),T); -end; - -info ={}; -info.b = b; -info.bc = bc; -info.X = X; -info.H =H; - -end - -% END MAIN FUNCTION -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% -% Subfunctions -% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -function [h, dh, dh2] = CanonicalBasisSet(TR) - -len = round(30/TR); -xBF.dt = TR; -xBF.length= len; -xBF.name = 'hrf (with time and dispersion derivatives)'; -xBF = spm_get_bf(xBF); - -v1 = xBF.bf(1:len,1); -v2 = xBF.bf(1:len,2); -v3 = xBF.bf(1:len,3); - -h = v1; -dh = v2 - (v2'*v1/norm(v1)^2).*v1; -dh2 = v3 - (v3'*v1/norm(v1)^2).*v1 - (v3'*dh/norm(dh)^2).*dh; - -h = h./max(h); -dh = dh./max(dh); -dh2 = dh2./max(dh2); - -end \ No newline at end of file diff --git a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Fit_Logit.m b/CanlabCore/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Fit_Logit.m deleted file mode 100644 index 898cc6ba..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Fit_Logit.m +++ /dev/null @@ -1,57 +0,0 @@ -function [hrf, fit, e, param] = Fit_Logit(tc,Run,t,mode) -% function [hrf, fit, e, param] = Fit_Logit(tc,Run,t,mode) -% -% Fits FIR and smooth FIR model -% -% INPUTS: -% -% tc - time course -% TR - time resolution -% Runs - expermental design -% T - length of estimated HRF -% mode - deterministic or stochastic -% options: -% 0 - deterministic aproach -% 1 - simulated annealing approach -% Please note that when using simulated annealing approach you -% may need to perform some tuning before use. -% -% OUTPUTS: -% -% hrf - estimated hemodynamic response function -% fit - estimated time course -% e - residual time course -% param - estimated amplitude, height and width -% -% Created by Martin Lindquist on 10/02/09 -% Last edited: 05/26/10 (ML) - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Fit the Logit model - -numstim = length(Run); -len = length(Run{1}); - -V0 = [ 1 6 1 0.5 10 1 15]; % initial values for logit fit -V0 = repmat(V0,1,numstim); - -if (mode == 1 && numstim>1), - disp('Multi-stimulus annealing function currently not implemented. Switching to "deterministic mode"') - mode = 0; -end; - -% Estimate theta (logit parameters) - -if (mode == 1) - disp('Stochastic Mode'); - - Runs = Run{1}; - [theta,HH,C,P,hrf,fit,e,param] = Anneal_Logit(V0,t,tc,Runs); - -elseif (mode == 0) - disp('Deterministic Mode'); - [theta, hrf, fit, e, param] = Det_Logit(V0,t,tc,Run); - -end - -end diff --git a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Fit_Logit_allstim.m b/CanlabCore/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Fit_Logit_allstim.m deleted file mode 100644 index bb697d63..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Fit_Logit_allstim.m +++ /dev/null @@ -1 +0,0 @@ -function [VM, hrf, fit, e, param] = Fit_Logit_allstim(V0,tc,TR, Run, T) % Fit inverse logit function model to time course % Multi-stimulus case % % INPUT: % % V0 = initial parameters % tc = time course % TR = time resolution % Run = stick function (one trial type per cell) % % % OUTPUT: % % VM = estimated parameters % hrf = estimated HRF % C = SSE % fit - fitted time course % param - estimated height, time to peak and width for each HRF numstim = length(Run); len = length(Run{1}); t=1:TR:T; % Initial values LB = [0.05, 1, 0, 0.05, 5, 0.05, 8]; % Previous Lower bounds for parameters UB = [5, 10, 2.5, 2, 15, 2, 20]; LB = repmat(LB, 1, numstim); UB = repmat(UB, 1, numstim); V0 = repmat(V0, 1, numstim); % Find optimal values options = optimset('MaxFunEvals',10000,'Maxiter',10000,'TolX', 1e-6, 'TolFun', 1e-6,'Display','off'); VM = fminsearchbnd(@cost_allstim, V0, LB,UB,options,t,tc,Run); % Use optimal values to fit hemodynamic response functions hrf =zeros(length(t),numstim); fitt = zeros(len,numstim); param = zeros(3,numstim); for g = 1:numstim hrf(:,g) = Get_Logit(VM(g*7-6:g*7),t); % Calculate HRF estimate (fit, given theta) param(:,g) = get_parameters2(hrf(:,g),(1:length(t))); end % Get fitted time courses and residuals for g = 1:numstim fits(:,g) = conv(Run{g}, hrf(:,g)); fitt(:,g) = fits(1:len,g); end fit = sum(fitt,2); e = tc-fit; return; \ No newline at end of file diff --git a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Fit_Logit_allstim_2.m b/CanlabCore/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Fit_Logit_allstim_2.m deleted file mode 100644 index 95139b90..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Fit_Logit_allstim_2.m +++ /dev/null @@ -1 +0,0 @@ -function [VM, hrf, fit] = Fit_Logit_allstim_2(V0,tc,tr, Run, down) % Fit inverse logit function model to time course % Multi-condition case % % Last edited by ML on 02/12/13 % % INPUTS: % % V0 - Initial values for IL-model % tc - time course data % tr - TR % Run - a cell array containing stick functions (one for each condition) % down - downsampling factor % % OUTPUTS: % % VM - final parameter values for IL-model % hrf - estimated HRFs for each condition % fit - estimated time course % Initial values numstim = length(Run); len = length(Run{1}); LB = [0.05, 1, 0, 0.05, 5, 0.05, 8]; % Previous Lower bounds for parameters UB = [2 10, 2, 2, 15, 1, 20]; % LB = [0.05, 1, 0, 0.05, 5, 0.05, 8, 0]; % Previous Lower bounds for parameters % UB = [2 10, 5, 2, 15, 1, 20, 5]; LB = repmat(LB, 1, numstim); UB = repmat(UB, 1, numstim); V0 = repmat(V0,1,numstim); % Find optimal values %options = optimset('MaxFunEvals',10000,'Maxiter',10000,'TolX', 1e-4, 'TolFun', 1e-4,'Display','Final'); options = optimset('MaxFunEvals',100000,'Maxiter',100000,'TolX', 1e-8, 'TolFun', 1e-8,'Display','off'); %VM = fminsearch(@msq_timecourse,V02,options,t,tc,RunA,RunB,RunC ); VM = fminsearchbnd(@cost_allstim_2, V0, LB, UB, options,tr,tc,Run,down); % Use optimal values to fit hemodynamic response functions t=0:(1/down):30; hrf =zeros(length(t),numstim); fitt = zeros(len,numstim); for g = 1:numstim hrf(:,g) = Get_Logit(VM(g*7-6:g*7),t); % Calculate HRF estimate (fit, given theta) end for g = 1:numstim fits(:,g) = conv(Run{g}, hrf(:,g)); fitt(:,g) = fits(1:len,g); end fit = sum(fitt,2); return; \ No newline at end of file diff --git a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Fit_sFIR.m b/CanlabCore/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Fit_sFIR.m deleted file mode 100644 index 2e76d50d..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Fit_sFIR.m +++ /dev/null @@ -1,77 +0,0 @@ -function [hrf, fit, e, param] = Fit_sFIR(tc,TR,Run,T,mode) -% function [hrf, fit, e, param] = Fit_sFIR(tc,TR,Runs,T,mode) -% -% Fits FIR and smooth FIR model -% -% INPUTS: -% -% tc - time course -% TR - time resolution -% Runs - expermental design -% T - length of estimated HRF -% mode - FIR or smooth FIR -% options: -% 0 - standard FIR -% 1 - smooth FIR -% -% OUTPUTS: -% -% hrf - estimated hemodynamic response function -% fit - estimated time course -% e - residual time course -% param - estimated amplitude, height and width -% -% Created by Martin Lindquist on 10/02/09 -% Last edited: 05/26/10 (ML) - -numstim = length(Run); -len = length(Run{1}); - - -Runs = zeros(len,numstim); -for i=1:numstim, - Runs(:,i) = Run{i}; -end; - -[DX] = tor_make_deconv_mtx3(Runs,T,1); - -% DX2 = DX(:,1:T); -% num = T; - -if mode == 1 - - C=(1:T)'*(ones(1,T)); - h = sqrt(1/(7/TR)); % 7 seconds smoothing - ref. Goutte - - v = 0.1; - sig = 1; - - R = v*exp(-h/2*(C-C').^2); - RI = inv(R); - MRI = zeros(numstim*T+1); - for i=1:numstim, - MRI(((i-1)*T+1):(i*T),((i-1)*T+1):(i*T)) = RI; - end; - - b = inv(DX'*DX+sig^2*MRI)*DX'*tc; - fit = DX*b; - e = tc - DX*b; - -elseif mode == 0 - - b = pinv(DX)*tc; - fit = DX*b; - e = tc - DX*b; - -end - - -hrf =zeros(T,numstim); -param = zeros(3,numstim); - -for i=1:numstim, - hrf(:,i) = b(((i-1)*T+1):(i*T))'; - param(:,i) = get_parameters2(hrf(:,i),T); -end; - -end \ No newline at end of file diff --git a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Get_Logit.m b/CanlabCore/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Get_Logit.m deleted file mode 100644 index c33a6d43..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Get_Logit.m +++ /dev/null @@ -1 +0,0 @@ -function [h, base] = get_logit(V,t) % % [h] = get_logit(V,t) % % Calculate inverse logit (IL) HRF model % Creates fitted curve - 3 logistic functions to be summed together - from parameter estimates % % INPUT: V, t % t = vector of time points % V = parameters % % By Martin Lindquist and Tor Wager % Edited 12/12/06 % A1 = V(1); T1 = V(2); d1 = V(3); A2 = V(4); T2 = V(5); A3 = V(6); T3 = V(7); d2 = -d1*(ilogit(A1*(1-T1)) - ilogit(A3*(1-T3)))/(ilogit(A2*(1-T2)) + ilogit(A3*(1-T3))); d3 = abs(d2)-abs(d1); h = d1*ilogit(A1*(t-T1))+d2*ilogit(A2*(t-T2))+d3*ilogit(A3*(t-T3)); % Superimpose 3 IL functions h = h'; base =zeros(3,length(t)); base(1,:) = ilogit(A1*(t-T1)); base(2,:) = ilogit(A2*(t-T2)); base(3,:) = ilogit(A3*(t-T3)); return \ No newline at end of file diff --git a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Get_Logit_2.m b/CanlabCore/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Get_Logit_2.m deleted file mode 100644 index 2930b4e7..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Get_Logit_2.m +++ /dev/null @@ -1 +0,0 @@ -function [h, base] = get_logit_2(V,t) % % [h] = get_logit(V,t) % % Calculate inverse logit (IL) HRF model % Creates fitted curve - 3 logistic functions to be summed together - from parameter estimates % % INPUT: V, t % t = vector of time points % V = parameters % % By Martin Lindquist and Tor Wager % Edited 12/12/06 % T = t(end); A1 = V(1); T1 = V(2); d1 = V(3); A2 = V(4); T2 = V(5); A3 = V(6); T3 = V(7); d3 = V(8); % d2 = -d1*(ilogit(A1*(1-T1)) - ilogit(A3*(1-T3)))/(ilogit(A2*(1-T2)) + ilogit(A3*(1-T3))); % d3 = abs(d2)-abs(d1); m1 = ilogit(A1*(1-T1)); m2 = ilogit(A2*(1-T2)); m3 = ilogit(A3*(1-T3)); w1 = ilogit(A1*(T-T1)); w2 = ilogit(A2*(T-T2)); w3 = ilogit(A3*(T-T3)); d2 = -d1*(m1 - m3*w1/w3)/(m2 - m3*w2/3); h = d1*ilogit(A1*(t-T1))+d2*ilogit(A2*(t-T2))+d3*ilogit(A3*(t-T3)); % Superimpose 3 IL functions h = h'; base =zeros(3,length(t)); base(1,:) = ilogit(A1*(t-T1)); base(2,:) = ilogit(A2*(t-T2)); base(3,:) = ilogit(A3*(t-T3)); return \ No newline at end of file diff --git a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Inverse_Logit_Tool_Instructions.rtf b/CanlabCore/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Inverse_Logit_Tool_Instructions.rtf deleted file mode 100644 index 790a7dbc..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/Inverse_Logit_Tool_Instructions.rtf +++ /dev/null @@ -1,69 +0,0 @@ -{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf420 -{\fonttbl\f0\fswiss\fcharset77 Helvetica;\f1\fswiss\fcharset77 CenturyGothic-BoldItalic;} -{\colortbl;\red255\green255\blue255;\red0\green6\blue240;} -\margl1440\margr1440\vieww5880\viewh19560\viewkind0 -\pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\ql\qnatural\pardirnatural - -\f0\fs36 \cf0 Inverse Logit Hemodynamic Response Estimation Toolbox -\fs24 \ -\ - -\f1\i\b\fs28 Version: -\f0\i0\b0\fs24 \ -12/12/06, Last Edit: 12/12/06\ -\ - -\f1\i\b\fs28 Authors: -\f0\i0\b0\fs24 \ -Martin Lindquist, Dept. of Statistics, Columbia University\ -Tor Wager, Dept. of Psychology, Columbia University\ -\ - -\f1\i\b\fs28 Reference: -\f0\i0\b0\fs24 \ -\ -\pard\pardeftab720\sa360\ql\qnatural -{\field{\*\fldinst{HYPERLINK "http://www.columbia.edu/cu/psychology/tor/Papers/Model_hrf-1.doc"}}{\fldrslt \cf0 \ul \ulc2 Lindquist, M. and Wager, T. D.\'ca (in press). Validity and Power in Hemodynamic Response Modeling:\'ca A comparison study and a new approach.\'ca \ulnone Human Brain Mapping (2007); early version available online.}}\ -\pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\ql\qnatural\pardirnatural -\cf0 \ - -\f1\i\b\fs28 Purpose:\ -\ - -\f0\i0\b0\fs24 This toolbox will estimate parameters for a flexible hemodynamic response function (HRF) using three superimposed inverse logit functions. The HRF is fit using seven parameters, and can capture a variety of HRF shapes, including sustained activations. \ -\ -Main inputs include a data timeseries and indicator (stick) function for the onsets of events. -\f1\i\b\fs28 \ -\ - -\f0\i0\b0\fs24 The function can be used with ROI timeseries or incorporated into whole-brain HRF-estimation tools. -\f1\i\b\fs28 \ -\ - -\f0\i0\b0\fs24 \ - -\f1\i\b\fs28 Main Functions:\ - -\f0\i0\b0\fs24 \ -(Specific help for each function is available by typing\ ->> help myfunctionname \ -at the >> matlab prompt.)\ -\ -Example.m\ --------------------------------------------------------------------------------------------------\ -Example script that loads example timeseries data and fits the model. Plots results.\ -\ -Anneal_Logit.m\ --------------------------------------------------------------------------------------------------\ -Main function for fitting inverse logit (IL) HRF estimate to timeseries.\ -Simulated annealing is built into function as a solution-finder.\ -\ -Get_Logit.m\ --------------------------------------------------------------------------------------------------\ -Create HRF estimate from IL parameters\ -\ -cost.m\ --------------------------------------------------------------------------------------------------\ -Least-squares cost function for the IL model\ -\ -} \ No newline at end of file diff --git a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/PowerLoss.m b/CanlabCore/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/PowerLoss.m deleted file mode 100644 index bbd855a9..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/PowerLoss.m +++ /dev/null @@ -1,38 +0,0 @@ -function [PowLoss] = PowerLoss(modres, modfit, moddf, tc, TR, Run, alpha) -% function [PowLoss] = PowerLoss(modres, modfit, moddf, tc, TR, Run, alpha) -% -% Estimates Power-loss due to mis-modeling. -% -% INPUT: -% -% modres - residuals -% modfit - model fit -% moddf - model degrees of freedom -% tc - time course -% TR - time resolution -% Runs - expermental design -% alpha - alpha value -% -% OUTPUT: -% -% PowLoss - Estimated power loss -% -% - -len = length(tc); % length of time course -T = round(30./TR); % length of estimated HRF -tstar = tinv(1-alpha,moddf); % t-threshold - -% Fit FIR model to find 'baseline' power. -[h, fit, e] = Fit_sFIR(tc,TR,Run,T,1); -s = (1/(len-T))*e'*e; -t = 1/sqrt(s*inv(fit'*fit)); -basePow = 1- nctcdf(tstar,(len-T),t); - -% Compute model power. -sig = (1/moddf)*modres'*modres; -ts = 1/sqrt(sig*inv(modfit'*modfit)); -modPow = 1- nctcdf(tstar,moddf,ts); - -% Compute 'power loss' -PowLoss = basePow - modPow; diff --git a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/PowerSim.m b/CanlabCore/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/PowerSim.m deleted file mode 100644 index 8dc57491..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/PowerSim.m +++ /dev/null @@ -1,37 +0,0 @@ -N = 1000; -M = 10; -P = zeros(N,M); -B = zeros(N,M); - -Yind = zeros(60,1); -Yind(21:40) = 1; -X2 = Yind; -sigma = 0.5; -tstar = tinv(1-0.05,59); - -for i=1:M, - for rep = 1:N, - - X = zeros(60,1); - X((20+i):(40+i-1)) = 1; - Y = Yind + normrnd(0,sigma,60,1); - b = pinv(X)*Y; - e = Y-X*b; - sig = sqrt(e'*e/59); - t = b./(sig/(X'*X)); - delta = 1/sig; - Pow = 1- nctcdf(tstar,59,delta); - - b2 = pinv(X2)*Y; - e2 = Y-X2*b2; - sig2 = sqrt(e2'*e2/59); - t2 = b2./(sig2/(X2'*X2)); - delta2 = 1/sig2; - Pow2 = 1- nctcdf(tstar,59,delta2); - - - P(rep,i) = Pow2-Pow; - B(rep,i) = b-1; - - end; -end; diff --git a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/ResidScan.m b/CanlabCore/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/ResidScan.m deleted file mode 100644 index 071cb589..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/ResidScan.m +++ /dev/null @@ -1,107 +0,0 @@ -function [p sres sres_ns] = ResidScan(res, FWHM) -% function [p sres sres_ns] = ResidScan(res, FWHM) -% -% Calculates P(M>=t) where M is the max value of the smoothed residuals. -% In this implementation the residuals are smoothed using a Gaussian -% kernel. -% -% INPUT: -% -% res - residual time course -% FWHM - Full Width Half Maximum (in time units) -% -% OUTPUT: -% -% p - pvalues -% sres - smoothed residuals -% sres_ns - smoothed residuals (non standardized) -% -% By Martin Lindquist & Ji-Meng Loh, July 2007 -% -% Edited by ML on 10/02/09 - -res_ns = res; -res = res./std(res); -len = length(res); - -% Create Gaussian Kernel -sig = ceil(FWHM/(2*sqrt(2*log(2)))); -klen = 3*sig; -kern = normpdf((-klen:klen),0,sig); -kern = kern./sqrt(sum(kern.^2)); - -% Convolve -x = conv(res,kern); -sres = x((klen + 1):(end-klen)); - -x = conv(res_ns,kern/sum(kern)); -sres_ns = x((klen + 1):(end-klen)); - - -% Find Max value -[a,location] = max(abs(sres)); - -% Find p-values using Gaussian Random Field theory -z = Euler_p(1, a, len, FWHM); -z = 2*z; %Two-sided test -p = min(1, z); - -end - -% END MAIN FUNCTION - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% -% Subfunctions -% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - - -function pval = Euler_p(myDim, value, N, fwhm) -% function z = Euler_p(myDim, value, N, fwhm) -% -% Finds the p value using the expected Euler characteristic. -% -% This function returns P(M \ge value) using the approximation -% \sum_{d=0}^D R_d(V) \rho_d(value) following Worsley et al's "A Unified -% Statistical Approach for Determining Significant Signals in Images of -% Cerebral Activation". -% -% INPUTS: -% -% myDim - the number of dimensions in the data -% value - the value of the maximum. -% N - the number of (time) points in that 1 dimension -% fwhm - the full width half maximum -% -% OUTPUTS: -% -% pval - the p-value - -% NOTE: CURRENTLY THIS FUNCTION IS ONLY IMPLEMENTED FOR THE 1D CASE - - % Constants - myfactor = 4*log(2); - pi2 = 2*pi; - exptsq = exp(-(value^2)/2); - - % Euler Characteristc Densties - rho = zeros(5,1); - rho(1) = 1-normcdf(value); - rho(2) = myfactor^(0.5)*exptsq/pi2; - rho(3) = myfactor * exptsq * value / (pi2 ^ (1.5)); - rho(4) = myfactor ^ (1.5) * exptsq * (value^2-1) / (pi2 ^2); - rho(5) = myfactor ^2 * exptsq * (value^3-3*value) / (pi2 ^ (5/2)); - - % Resel Count - R0 = 1; - R1 = N/fwhm; - - % P-value - pval = R0 * rho(1) + R1 * rho(2); - -end - - diff --git a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/ScanSim4.m b/CanlabCore/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/ScanSim4.m deleted file mode 100644 index 803d202b..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/ScanSim4.m +++ /dev/null @@ -1,67 +0,0 @@ -TR = 0.5; -len = 200; - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Compute hrf -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -t=0.1:TR:30; -a1 =6; -a2 = 12; -b1 = 0.9; -b2 =0.9; -c = 0.35; - -d1 = a1*b1; -d2 = a2*b2; - -h = ((t./d1).^a1).*exp(-(t-d1)./b1) - c*((t./d2).^a2).*exp(-(t-d2)./b2); -h = h./max(h); - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -% X = zeros(len,4); -% X(:,1) = 1; -% X(:,2) = (1:len)/len; -% X(:,3) = X(:,2).^2; -% -% Run = zeros(40,5); -% Run(1:20,:) = 1; -% Run = reshape(Run,200,1); -% q = conv(Run,h); -% X(:,4) = q(1:len); - - -X = zeros(len,2); -X(:,1) = 1; -Run = zeros(200,1); -Run(50) = 1; -Run(150) = 1; -q = conv(Run,h); -X(:,2) = q(1:len); - - -Run = zeros(200,1); -Run(50) = 1; -Run(160) = 1; -tc = conv(Run,h); -tc = tc(1:len) + normrnd(0,0.3,200,1); - - -beta = pinv(X)*tc; -e = tc-X*beta; -sigma = sqrt((1/(len-size(X,2)))*sum(e.^2)); -%c = [0 0 0 1]; -c = [0 1]; -se = sigma.*sqrt(c*inv(X'*X)*c'); -t = beta/se; -pval = 2*tcdf(-abs(t),len-4); -df = len - 4; - -[z sres sres_ns] = ResidScan(e, 4); -[b bias pl pc pe] = BiasPowerloss(tc, X,c,beta,df,z,0.05); - -beta -b -bias -pl diff --git a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/get_parameters2.m b/CanlabCore/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/get_parameters2.m deleted file mode 100644 index 2793c63d..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/get_parameters2.m +++ /dev/null @@ -1,43 +0,0 @@ -function [param] = get_parameters2(hdrf,t) - -% Find model parameters -% -% Height - h -% Time to peak - p (in time units of TR seconds) -% Width (at half peak) - w - - -% Calculate Heights and Time to peak: - -n = t(end)*0.6; - -[h,p] = max(abs(hdrf(1:n))); -h = hdrf(p); - -if (p > t(end)*0.6), warning('Late time to peak'), end; - -if (h >0) - v = (hdrf >= h/2); -else - v = (hdrf <= h/2); -end; - -[a,b] = min(diff(v)); -v(b+1:end) = 0; -w = sum(v); - -cnt = p-1; -g =hdrf(2:end) - hdrf(1:(end-1)); -while((cnt > 0) & (abs(g(cnt)) <0.001)), - h = hdrf(cnt); - p = cnt; - cnt = cnt-1; -end; - - -param = zeros(3,1); -param(1) = h; -param(2) = p; -param(3) = w; - -return; diff --git a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/ilogit.m b/CanlabCore/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/ilogit.m deleted file mode 100644 index bce2823a..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/ilogit.m +++ /dev/null @@ -1,13 +0,0 @@ -function [L] = ilogit(t) -% -% function [L] = ilogit(t) -% -% Calculate the inverse logit function corresponding to the value t -% -% OUTPUT: L = exp(t)./(1+exp(t)); -% -% By Martin Lindquist and Tor Wager -% Edited 12/12/06 -% - -L = exp(t)./(1+exp(t)); \ No newline at end of file diff --git a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/timecourse.mat b/CanlabCore/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/timecourse.mat deleted file mode 100644 index fb694160..00000000 Binary files a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/timecourse.mat and /dev/null differ diff --git a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/timecourse_2.mat b/CanlabCore/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/timecourse_2.mat deleted file mode 100644 index 7a1c0700..00000000 Binary files a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/timecourse_2.mat and /dev/null differ diff --git a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/timecourse_old.mat b/CanlabCore/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/timecourse_old.mat deleted file mode 100644 index fb694160..00000000 Binary files a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/More_recent_old_stuff/timecourse_old.mat and /dev/null differ diff --git a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/ScanSim4.m b/CanlabCore/HRF_Est_Toolbox2/Old_stuff/ScanSim4.m deleted file mode 100644 index 803d202b..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/ScanSim4.m +++ /dev/null @@ -1,67 +0,0 @@ -TR = 0.5; -len = 200; - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Compute hrf -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -t=0.1:TR:30; -a1 =6; -a2 = 12; -b1 = 0.9; -b2 =0.9; -c = 0.35; - -d1 = a1*b1; -d2 = a2*b2; - -h = ((t./d1).^a1).*exp(-(t-d1)./b1) - c*((t./d2).^a2).*exp(-(t-d2)./b2); -h = h./max(h); - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -% X = zeros(len,4); -% X(:,1) = 1; -% X(:,2) = (1:len)/len; -% X(:,3) = X(:,2).^2; -% -% Run = zeros(40,5); -% Run(1:20,:) = 1; -% Run = reshape(Run,200,1); -% q = conv(Run,h); -% X(:,4) = q(1:len); - - -X = zeros(len,2); -X(:,1) = 1; -Run = zeros(200,1); -Run(50) = 1; -Run(150) = 1; -q = conv(Run,h); -X(:,2) = q(1:len); - - -Run = zeros(200,1); -Run(50) = 1; -Run(160) = 1; -tc = conv(Run,h); -tc = tc(1:len) + normrnd(0,0.3,200,1); - - -beta = pinv(X)*tc; -e = tc-X*beta; -sigma = sqrt((1/(len-size(X,2)))*sum(e.^2)); -%c = [0 0 0 1]; -c = [0 1]; -se = sigma.*sqrt(c*inv(X'*X)*c'); -t = beta/se; -pval = 2*tcdf(-abs(t),len-4); -df = len - 4; - -[z sres sres_ns] = ResidScan(e, 4); -[b bias pl pc pe] = BiasPowerloss(tc, X,c,beta,df,z,0.05); - -beta -b -bias -pl diff --git a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/cost.m b/CanlabCore/HRF_Est_Toolbox2/Old_stuff/cost.m deleted file mode 100644 index 589007e6..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/cost.m +++ /dev/null @@ -1 +0,0 @@ -function Q = cost(V,t,tc,Run) % % Least-squares cost function for the IL model % % INPUT: % Run = stick function % tc = time course % t = vector of time points % V = parameters % % OUTPUT: % Q = cost % % By Martin Lindquist and Tor Wager % Edited 12/12/06 % len = length(Run); h1 = Get_Logit(V(1:7),t); % Get IL model corresponding to parameters V yhat = conv(Run, h1); % Convolve IL model with stick function yhat = yhat(1:len); Q = sum((yhat-tc).^2); % Calculate cost function return \ No newline at end of file diff --git a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/cost2.m b/CanlabCore/HRF_Est_Toolbox2/Old_stuff/cost2.m deleted file mode 100644 index 0aa67b4c..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/cost2.m +++ /dev/null @@ -1 +0,0 @@ -function Q = cost2(V,t,tc,RunA,RunB) % % inverse logit -- creates fitted curve from parameter estimates % % t = vector of time points % V = parameters % % 3 logistic functions to be summed together % % h1 = get_logit(V(1:7),t); % h2 = get_logit(V(8:14),t); % h1 = get_logit(V(1:8),t); % h2 = get_logit(V(9:16),t); type = 7; if (type == 7), h1 = get_logit(V(1:7),t); h2 = get_logit(V(8:14),t); elseif(type == 9), h1 = get_logit(V(1:9),t); h2 = get_logit(V(10:18),t); end; len = length(RunA); tcA = conv(RunA, h1); tcA = tcA(1:len); len = length(RunB); tcB = conv(RunB, h2); tcB = tcB(1:len); yhat = tcA + tcB; Q = sum((yhat-tc).^2); return \ No newline at end of file diff --git a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/cost_allstim.m b/CanlabCore/HRF_Est_Toolbox2/Old_stuff/cost_allstim.m deleted file mode 100644 index 1255cd64..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/cost_allstim.m +++ /dev/null @@ -1,33 +0,0 @@ -function Q = cost_allstim(V,t,tc,Run) -% -% Least-squares cost function for the IL model -% -% INPUT: -% Run = stick function -% tc = time course -% t = vector of time points -% V = parameters -% -% OUTPUT: -% Q = cost -% -% By Martin Lindquist and Tor Wager -% Edited 12/12/06 -% Further edited by Christian Waugh 2/15/08 to include multiple trialtypes - -numstim = length(Run); -len = length(Run{1}); -h = zeros(length(t),numstim); -yhatt =zeros(len,numstim); - -for k = 1:numstim - h(:,k) = Get_Logit(V(k*7-6:k*7),t); % Get IL model corresponding to parameters V - yhat(:,k) = conv(Run{k}, h(:,k)); % Convolve IL model with stick function - yhatt(:,k) = yhat(1:len,k); -end - -yhat2 = sum(yhatt,2); %Sum models together to get overall estimate - -Q = sum((yhat2-tc).^2); % Calculate cost function - -return \ No newline at end of file diff --git a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/cost_allstim_2.m b/CanlabCore/HRF_Est_Toolbox2/Old_stuff/cost_allstim_2.m deleted file mode 100644 index 9c98d690..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/cost_allstim_2.m +++ /dev/null @@ -1,43 +0,0 @@ -function Q = cost_allstim_2(V,tr,tc,Run,down) -% -% Least-squares cost function for the IL model -% Multi-condition case -% -% INPUT: -% down = downsampling factor -% Run = stick function -% tc = time course -% tr = repetition time -% V = parameters -% -% OUTPUT: -% Q = cost -% -% By Martin Lindquist and Tor Wager -% Edited 12/12/06 -% Further edited by Christian Waugh 2/15/08 to include multiple trialtypes -% Edited by ML on 02/12/13 - -t = 0:(1/down):30; - -numstim = length(Run); -len = length(Run{1}); -h = zeros(length(t),numstim); -yhatt =zeros(len,numstim); - - -for k = 1:numstim - - h(:,k) = Get_Logit(V(k*7-6:k*7),t); % Get IL model corresponding to parameters V - yhat(:,k) = conv(Run{k}, h(:,k)); % Convolve IL model with stick function - yhatt(:,k) = yhat(1:len,k); - -end - -tt = 1:(tr*down):len; - -yhat2 = sum(yhatt,2); %Sum models together to get overall estimate - -Q = sum((yhat2(tt)-tc).^2); % Calculate cost function - -return \ No newline at end of file diff --git a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/cost_allstim_3.m b/CanlabCore/HRF_Est_Toolbox2/Old_stuff/cost_allstim_3.m deleted file mode 100644 index 003d502d..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/cost_allstim_3.m +++ /dev/null @@ -1,51 +0,0 @@ -function Q = cost_allstim_2(V,input) -% -% Least-squares cost function for the IL model -% Multi-condition case -% -% INPUT: -% down = downsampling factor -% Run = stick function -% tc = time course -% tr = repetition time -% V = parameters -% -% OUTPUT: -% Q = cost -% -% By Martin Lindquist and Tor Wager -% Edited 12/12/06 -% Further edited by Christian Waugh 2/15/08 to include multiple trialtypes -% Edited by ML on 02/12/13 -% Edited by ML on 08/18/18 - - - -tr = input{1}; -tc = input{2}; -Run = input{3}; -down = input{4}; - -t = 0:(tr/down):30; - -numstim = length(Run); -len = length(Run{1}); -h = zeros(length(t),numstim); -yhatt =zeros(len,numstim); - - -for k = 1:numstim - - h(:,k) = Get_Logit(V(k*7-6:k*7),t); % Get IL model corresponding to parameters V - yhat(:,k) = conv(Run{k}, h(:,k)); % Convolve IL model with stick function - yhatt(:,k) = yhat(1:len,k); - -end - -tt = 1:(1*down):len; - -yhat2 = sum(yhatt,2); %Sum models together to get overall estimate - -Q = sum((yhat2(tt)-tc).^2); % Calculate cost function - -return \ No newline at end of file diff --git a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/dilogit_dt2.m b/CanlabCore/HRF_Est_Toolbox2/Old_stuff/dilogit_dt2.m deleted file mode 100644 index b0dd817b..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/Old_stuff/dilogit_dt2.m +++ /dev/null @@ -1,35 +0,0 @@ -function [dLdt, d2Ldt2, d1, d2] = dilogit_dt2(t,V) -% -% [dLdt, d2Ldt2, d1, d2] = dilogit_dt2(t,V) -% -% Calculate first and second temoral derivative of inverse logit (IL) HRF model -% curve -% -% INPUT: V, t -% t = vector of time points -% V = parameters -% -% OUTPUT: dLdt, d2Ldt2 -% dLdt = first temporal derivative of IL model -% d2Ldt2 = second temporal derivative of IL model -% -% By Martin Lindquist and Tor Wager -% Edited 12/12/06 -% - -% Set parameter values -A1 = V(1); -T1 = V(2); -d1 = V(3); -A2 = V(4); -T2 = V(5); -A3 = V(6); -T3 = V(7); -d2 = -d1*(ilogit(A1*(1-T1)) - ilogit(A3*(1-T3)))/(ilogit(A2*(1-T2)) + ilogit(A3*(1-T3))); -d3 = abs(d2)-abs(d1); - -% Calculate the first and second derivative -dLdt = d1*A1*ilogit(A1*(t-T1))./(1+exp(A1*(t-T1))) + d2*A2*ilogit(A2*(t-T2))./(1+exp(A2*(t-T2))) + d3*A3*ilogit(A3*(t-T3))./(1+exp(A3*(t-T3))); -d2Ldt2 = d1*(A1^2)*(exp(A1*(t-T1)) - exp(2*A1*(t-T1)))./((1+exp(A1*(t-T1))).^3) + d2*(A2^2)*(exp(A2*(t-T2)) - exp(2*A2*(t-T2)))./((1+exp(A2*(t-T2))).^3) + d3*(A3^2)*(exp(A3*(t-T3)) - exp(2*A3*(t-T3)))./((1+exp(A3*(t-T3))).^3); - -return; \ No newline at end of file diff --git a/CanlabCore/HRF_Est_Toolbox2/PowerLoss.m b/CanlabCore/HRF_Est_Toolbox2/PowerLoss.m deleted file mode 100644 index 13897efa..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/PowerLoss.m +++ /dev/null @@ -1,39 +0,0 @@ -function [PowLoss] = PowerLoss(modres, modfit, moddf, tc, TR, Run, alpha) -% function [PowLoss] = PowerLoss(modres, modfit, moddf, tc, TR, Run, alpha) -% -% Estimates Power-loss due to mis-modeling. -% -% INPUT: -% -% modres - residuals -% modfit - model fit -% moddf - model degrees of freedom -% tc - time course -% TR - time resolution -% Runs - expermental design -% alpha - alpha value -% -% OUTPUT: -% -% PowLoss - Estimated power loss -% -% - -len = length(tc); % length of time course -%T = round(30./TR); % length of estimated HRF -T = 30; -tstar = tinv(1-alpha,moddf); % t-threshold - -% Fit FIR model to find 'baseline' power. -[h, fit, e] = Fit_sFIR(tc,TR,Run,T,1); -s = (1/(len-T))*e'*e; -t = 1/sqrt(s*inv(fit'*fit)); -basePow = 1- nctcdf(tstar,(len-T),t); - -% Compute model power. -sig = (1/moddf)*modres'*modres; -ts = 1/sqrt(sig*inv(modfit'*modfit)); -modPow = 1- nctcdf(tstar,moddf,ts); - -% Compute 'power loss' -PowLoss = basePow - modPow; diff --git a/CanlabCore/HRF_Est_Toolbox2/PowerSim.m b/CanlabCore/HRF_Est_Toolbox2/PowerSim.m deleted file mode 100644 index 8dc57491..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/PowerSim.m +++ /dev/null @@ -1,37 +0,0 @@ -N = 1000; -M = 10; -P = zeros(N,M); -B = zeros(N,M); - -Yind = zeros(60,1); -Yind(21:40) = 1; -X2 = Yind; -sigma = 0.5; -tstar = tinv(1-0.05,59); - -for i=1:M, - for rep = 1:N, - - X = zeros(60,1); - X((20+i):(40+i-1)) = 1; - Y = Yind + normrnd(0,sigma,60,1); - b = pinv(X)*Y; - e = Y-X*b; - sig = sqrt(e'*e/59); - t = b./(sig/(X'*X)); - delta = 1/sig; - Pow = 1- nctcdf(tstar,59,delta); - - b2 = pinv(X2)*Y; - e2 = Y-X2*b2; - sig2 = sqrt(e2'*e2/59); - t2 = b2./(sig2/(X2'*X2)); - delta2 = 1/sig2; - Pow2 = 1- nctcdf(tstar,59,delta2); - - - P(rep,i) = Pow2-Pow; - B(rep,i) = b-1; - - end; -end; diff --git a/CanlabCore/HRF_Est_Toolbox2/README.pptx b/CanlabCore/HRF_Est_Toolbox2/README.pptx deleted file mode 100644 index 5b106e7c..00000000 Binary files a/CanlabCore/HRF_Est_Toolbox2/README.pptx and /dev/null differ diff --git a/CanlabCore/HRF_Est_Toolbox2/ResidScan.m b/CanlabCore/HRF_Est_Toolbox2/ResidScan.m deleted file mode 100644 index 071cb589..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/ResidScan.m +++ /dev/null @@ -1,107 +0,0 @@ -function [p sres sres_ns] = ResidScan(res, FWHM) -% function [p sres sres_ns] = ResidScan(res, FWHM) -% -% Calculates P(M>=t) where M is the max value of the smoothed residuals. -% In this implementation the residuals are smoothed using a Gaussian -% kernel. -% -% INPUT: -% -% res - residual time course -% FWHM - Full Width Half Maximum (in time units) -% -% OUTPUT: -% -% p - pvalues -% sres - smoothed residuals -% sres_ns - smoothed residuals (non standardized) -% -% By Martin Lindquist & Ji-Meng Loh, July 2007 -% -% Edited by ML on 10/02/09 - -res_ns = res; -res = res./std(res); -len = length(res); - -% Create Gaussian Kernel -sig = ceil(FWHM/(2*sqrt(2*log(2)))); -klen = 3*sig; -kern = normpdf((-klen:klen),0,sig); -kern = kern./sqrt(sum(kern.^2)); - -% Convolve -x = conv(res,kern); -sres = x((klen + 1):(end-klen)); - -x = conv(res_ns,kern/sum(kern)); -sres_ns = x((klen + 1):(end-klen)); - - -% Find Max value -[a,location] = max(abs(sres)); - -% Find p-values using Gaussian Random Field theory -z = Euler_p(1, a, len, FWHM); -z = 2*z; %Two-sided test -p = min(1, z); - -end - -% END MAIN FUNCTION - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% -% Subfunctions -% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - - -function pval = Euler_p(myDim, value, N, fwhm) -% function z = Euler_p(myDim, value, N, fwhm) -% -% Finds the p value using the expected Euler characteristic. -% -% This function returns P(M \ge value) using the approximation -% \sum_{d=0}^D R_d(V) \rho_d(value) following Worsley et al's "A Unified -% Statistical Approach for Determining Significant Signals in Images of -% Cerebral Activation". -% -% INPUTS: -% -% myDim - the number of dimensions in the data -% value - the value of the maximum. -% N - the number of (time) points in that 1 dimension -% fwhm - the full width half maximum -% -% OUTPUTS: -% -% pval - the p-value - -% NOTE: CURRENTLY THIS FUNCTION IS ONLY IMPLEMENTED FOR THE 1D CASE - - % Constants - myfactor = 4*log(2); - pi2 = 2*pi; - exptsq = exp(-(value^2)/2); - - % Euler Characteristc Densties - rho = zeros(5,1); - rho(1) = 1-normcdf(value); - rho(2) = myfactor^(0.5)*exptsq/pi2; - rho(3) = myfactor * exptsq * value / (pi2 ^ (1.5)); - rho(4) = myfactor ^ (1.5) * exptsq * (value^2-1) / (pi2 ^2); - rho(5) = myfactor ^2 * exptsq * (value^3-3*value) / (pi2 ^ (5/2)); - - % Resel Count - R0 = 1; - R1 = N/fwhm; - - % P-value - pval = R0 * rho(1) + R1 * rho(2); - -end - - diff --git a/CanlabCore/HRF_Est_Toolbox2/get_parameters2.m b/CanlabCore/HRF_Est_Toolbox2/get_parameters2.m deleted file mode 100644 index 2d952db5..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/get_parameters2.m +++ /dev/null @@ -1,46 +0,0 @@ -function [param] = get_parameters2(hdrf,t) -% -% Find model parameters -% -% Height - h -% Time to peak - p (in time units of TR seconds) -% Width (at half peak) - w - - -% Calculate Heights and Time to peak: - -% delta = 1/(t(2)-t(1)); -% n = round(t(end)*0.6*delta) -n = round(length(t)*0.8); - -[~,p] = max(abs(hdrf(1:n))); -h = hdrf(p); - -%if (p > t(end)*0.6*delta), warning('Late time to peak'), end; -% if (p > t(end)*0.8), warning('Late time to peak'), end; - -if (h >0) - v = (hdrf >= h/2); -else - v = (hdrf <= h/2); -end; - -[~,b] = min(diff(v)); -v(b+1:end) = 0; -w = sum(v); - -cnt = p-1; -g =hdrf(2:end) - hdrf(1:(end-1)); -while((cnt > 0) && (abs(g(cnt)) <0.001)), - h = hdrf(cnt); - p = cnt; - cnt = cnt-1; -end; - - -param = zeros(3,1); -param(1) = h; -param(2) = p; -param(3) = w; - -return; diff --git a/CanlabCore/HRF_Est_Toolbox2/hrf_fit_one_voxel.m b/CanlabCore/HRF_Est_Toolbox2/hrf_fit_one_voxel.m deleted file mode 100644 index 22ff2156..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/hrf_fit_one_voxel.m +++ /dev/null @@ -1,145 +0,0 @@ -function [h, fit, e, param, info] = hrf_fit_one_voxel(tc,TR,Runc,T,method,mode, varargin) -% function [h, fit, e, param] = hrf_fit_one_voxel(tc,TR,Runc,T,type,mode) -% -% HRF estimation function for a single voxel; -% -% Implemented methods include: IL-model (Deterministic/Stochastic), FIR -% (Regular/Smooth), and HRF (Canonical/+ temporal/+ temporal & dispersion) -% -% INPUTS: -% -% tc - time course -% TR - time resolution -% Runs - expermental design -% T - length of estimated HRF -% type - Model type -% mode - Mode -% -% Options: p=1 - only canonical HRF -% p=2 - canonical + temporal derivative -% p=3 - canonical + time and dispersion derivative -% -% OUTPUTS: -% -% hrf - estimated hemodynamic response function -% fit - estimated time course -% e - residual time course -% param - estimated amplitude, height and width -% info - Design Matrix, Pseudoinverse information, Misc. -% -% Created by Martin Lindquist on 04/11/14 -% -% Added a varargin to accept a design matrix to modulate the model fit. -% - Michael Sun, Ph.D. 02/09/2024 - - -if ~isempty(varargin) - % Load in SPM structure if passed in - % timecourse tc must now be gKWY to match SPM output. - % Filtered Design Matrix SPM.xX.xKXs.X must be passed in instead of - % allowing Fit_* to generate its own design matrix. - - % if ischar(varargin{1}) || isstring(varargin{1}) - % load(varargin{1}) - % elseif isstruct(varargin{1}) - % - % SPM=varargin{1}; - % end - % - % SPM.xX.xKXs.X - - % SPM_DX=varargin{1}; - -end - - -if (strcmp(method,'IL')), - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Fit HRF using IL-function - -% Choose mode (deterministic/stochastic) - -% mode = 0; % 0 - deterministic aproach - % 1 - simulated annealing approach - % Please note that when using simulated annealing approach you - % may need to perform some tuning before use. - - if numel(T) > 1 - if all(abs(T(:) - T(1)) < 1e-8) - T = T(1); - else - error('Multiple time windows not yet implemented for IL: T values must be identical within tolerance.'); - end - end - - if ~isempty(varargin) - disp('IL-function with custom Design Matrix not yet implemented') - [h, fit, e, param] = Fit_Logit2(tc,TR,Runc,T,mode); - else - [h, fit, e, param] = Fit_Logit2(tc,TR,Runc,T,mode); - end - - param(2:3,:) = param(2:3,:).*TR; - - -elseif (strcmp(method,'FIR')), - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Fit HRF using FIR-model - -% Choose mode (FIR/sFIR) - -% mode = 1; % 0 - FIR - % 1 - smooth FIR - - % [h, fit, e, param] = Fit_sFIR(tc,TR,Runc,T,mode); - - if ~isempty(varargin) - [h, fit, e, param, info] = Fit_sFIR_epochmodulation(tc,TR,Runc,T,mode, varargin{:}); - - else - [h, fit, e, param, info] = Fit_sFIR_epochmodulation(tc,TR,Runc,T,mode); - end - - - - param(2:3,:) = param(2:3,:).*TR; - - -elseif (strcmp(method,'CHRF')), - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Fit HRF using Canonical HRF + 2 derivatives - -% mode = 0; % 0 - HRF - % 1 - HRF + temporal derivative - % 2 - HRF + temporal and dispersion derivative - - p = mode + 1; - - if numel(T) > 1 - if all(abs(T(:) - T(1)) < 1e-8) - T = T(1); - else - error('Multiple time windows not yet implemented for CHRF: T values must be identical within tolerance.'); - end - end - - if ~isempty(varargin) - [h, fit, e, param, info] = Fit_Canonical_HRF(tc,TR,Runc,T,p, varargin{:}); - else - [h, fit, e, param, info] = Fit_Canonical_HRF(tc,TR,Runc,T,p); - end - - - param(2:3,:) = param(2:3,:).*TR; - -else - warning('Incorrect Model Type. Use IL, FIR or CHRF'); -end - - - -end % function - diff --git a/CanlabCore/HRF_Est_Toolbox2/ilogit.m b/CanlabCore/HRF_Est_Toolbox2/ilogit.m deleted file mode 100644 index bce2823a..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/ilogit.m +++ /dev/null @@ -1,13 +0,0 @@ -function [L] = ilogit(t) -% -% function [L] = ilogit(t) -% -% Calculate the inverse logit function corresponding to the value t -% -% OUTPUT: L = exp(t)./(1+exp(t)); -% -% By Martin Lindquist and Tor Wager -% Edited 12/12/06 -% - -L = exp(t)./(1+exp(t)); \ No newline at end of file diff --git a/CanlabCore/HRF_Est_Toolbox2/sFIR_multisubject_example_script.m b/CanlabCore/HRF_Est_Toolbox2/sFIR_multisubject_example_script.m deleted file mode 100644 index d795b211..00000000 --- a/CanlabCore/HRF_Est_Toolbox2/sFIR_multisubject_example_script.m +++ /dev/null @@ -1,112 +0,0 @@ -function sFIR_estimation(subIDs) -% A wrapper function for estimating voxel-wise smooth FIR model fits for each subject -% in a multi-subject group, by Stephan Geuter. It uses the hrf_fit method for fmri_data objects, -% which calls the HRF_est_Toolbox2 functions. Future extensions could: -% - standardize the use of obj.images_per_session here, allowing flexibility in -% number of images per run -% - increase documentation -% - provide a complete walkthrough with data needed to run in our data -% repository - - -% folders -rdir = '/work/ics/data/projects/wagerlab/labdata/projects/YOURPROJECT/'; % root directory -odir = fullfile(rdir,'/data/onsets/'); % directory with onset files -adir = fullfile(rdir,'/analysis/'); % directory for analysis outputs -imgdir = fullfile(rdir,'data','mri'); % directory hold subdirectories with fmri data/nifti - -%%% - -% design spec -TR = 1.3; % TR in seconds -T = TR*23; % total duration of estimated (s)FIR response -fl = 'fl_D_cat_cue_on_sFIR'; % folder name for this analysis (sub directory of 'adir') - -nimgs = 1848; % n total images per subject -nrun = 4; % n runs per subject -img_run = nimgs/nrun; - - -% filters -imgfilt = 'swrarun_r*.nii'; % filter for nifti files of each subject -onsetfilt = 'cat_cue_on_stick.mat'; % filter for onset file for each subject - -% analysis mask -maskfile = fullfile(rdir,'masks','BasalGanglia','BG_T50.nii'); % fullpath to mask image -% standard mask: maskfile = which('brainmask.nii'); - - -%%% - -if ~isdir(fullfile(adir,fl)), mkdir(fullfile(adir,fl)); end - - -% loop subjects -nrun = numel(subIDs); -for crun = 1:nrun - - % current subject and folder - fprintf(1,'\nnow running subject %d...\n\n',subIDs(crun)); - fldir = fullfile(adir,fl,sprintf('sub%04d',subIDs(crun))); - if ~isdir(fldir), mkdir(fldir); end; - cd(fldir); - - % filter all image files - clear nimgs imgs - imgs = filenames(fullfile(imgdir,sprintf('sub%04d',subIDs(crun)),'run*',imgfilt)); - regnames = {}; - C = {}; - - % filter onset file - for o = 1:length(onsetfilt) - - % get onsets from subject specific onset directory - ons = filenames(sprintf('%ssub%04d/%s',odir,subIDs(crun),onsetfilt{o}),'char'); - load(ons); - - % select regressors - sel = find(cellregexp(names,'\w')); - - for k=1:length(sel) - - % make design cell array for HRF toolbox - C{end+1} = zeros(nimgs,1); - - C{end}(ceil(onsets{sel(k)})) = 1; - regnames{end+1} = names{sel(k)}; - % plot(C{k}+k,'color',hsv2rgb([1/numel(osel)*k,1,1])); hold on - % fprintf(1,'\n%s\t\t%d',names{k},sum(C{k})); - end - end - - % load data within mask - d = fmri_data(imgs, maskfile); - - % HP filter - [y, pI] = hpfilter(d.dat', TR, 160, repmat(img_run,nrun,1)); - - % add intercept back - y = y + pI * d.dat'; - - % scale SPM style - v = [0 1:nrun-1] * 462; - gs = []; - for r = 1:nrun - gs(r) = nanmean( nanmean( double(y(1+v(r):img_run+v(r),:)),2)); - end - gs = repmat(gs,img_run,1); gs = gs(:)'; - gs = repmat(gs,size(d.dat,1),1); - d.dat = y' * 100 ./ gs; - - % fit sFIR model. writes images in pwd (fldir) - [params_obj hrf_obj] = hrf_fit(d, TR, C, T, 'FIR', 1); - - % save results in .mat files - fn = fullfile(fldir,'sFIR_results.mat'); - save(fn,'params_obj','hrf_obj','onsetfilt','regnames','C','onsets'); - -end - - - -end \ No newline at end of file diff --git a/CanlabCore/HRF_Est_Toolbox2/timecourse.mat b/CanlabCore/HRF_Est_Toolbox2/timecourse.mat deleted file mode 100644 index fb694160..00000000 Binary files a/CanlabCore/HRF_Est_Toolbox2/timecourse.mat and /dev/null differ diff --git a/CanlabCore/HRF_Est_Toolbox4/Old_stuff/More_recent_old_stuff/Example_old.m b/CanlabCore/HRF_Est_Toolbox4/Old_stuff/More_recent_old_stuff/Example_old.m deleted file mode 100644 index 1ed0a257..00000000 --- a/CanlabCore/HRF_Est_Toolbox4/Old_stuff/More_recent_old_stuff/Example_old.m +++ /dev/null @@ -1,207 +0,0 @@ -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% -% Example code for estimating the HRF using the Inverse-Logit Model, a -% Finte Impluse Response Model and the Canonical HRF with 2 derivatives. -% Also the code illustrates our code for detecting model misspecification. -% -% By Martin Lindquist and Tor Wager -% Edited 05/17/13 -% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% -% Load time course -% - -mypath = which('ilogit'); -if isempty(mypath), error('Cannot find directory with ilogit.m and other functions. Not on path?'); end -[mydir] = fileparts(mypath) - -load(fullfile(mydir,'timecourse')) - -tc = (tc- mean(tc))/std(tc); -len = length(tc); - - -%% Or: create your own -[xBF] = spm_get_bf(struct('dt', .5, 'name', 'hrf (with time and dispersion derivatives)', 'length', 32)); -clear Xtrue -for i = 1:1, xx = conv(xBF.bf(:,i), [1 1 1 1 1 1 ]'); - Xtrue(:, i) = xx(1:66); -end -for i = 2:3, xx = conv(xBF.bf(:,i), [1 1]'); - Xtrue(:, i) = xx(1:66); -end -hrf = Xtrue * [1 .3 .2]'; -xsecs = 0:.5:32; -hrf = hrf(1:length(xsecs)); -hrf = hrf ./ max(hrf); -figure; plot(xsecs, hrf, 'k') -%hrf = hrf(1:4:end); % downsample to TR, if TR is > 0.5 - - -R = randperm(640); R = sort(R(1:36)); -Run = zeros(640,1); -for i=1:length(R), Run(R(i)) = 1; end; -true_sig = conv(Run, hrf); -true_sig = true_sig(1:640); - -tc_noise = noise_arp(640, [.7 .2]); -tc = true_sig + 0 * tc_noise; -%figure; plot(tc); - - -%% - -create_figure; subplot(3,1,1); han = plot(tc); -title('Sample time course'); drawnow - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% -% Settings -% - -TR = 0.5; -% T = round(30/TR); -% t = 1:T; % samples at which to get Logit HRF Estimate -T = 30; -FWHM = 4; % FWHM for residual scan -pval = 0.01; -df = 600; -alpha = 0.001; - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% -% Create stick function (sample event onsets) -% Variable R contains onset times -% Variable Run contains stick (a.k.a. delta or indicator) function - -R = [3, 21, 56, 65, 109, 126, 163, 171, 216, 232, 269, 282, 323, 341, 376, 385, 429, 446, 483, 491, 536, 552, 589, 602]; -Run = zeros(640,1); -for i=1:length(R), Run(R(i)) = 1; end; -Runc = {}; -Runc{1} = Run; - -try - hold on; - hh = plot_onsets(R,'k',-3,1); - drawnow -catch - disp('Couldn''t find function to add onset sticks to plot. Skipping.') -end - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Fit HRF using IL-function - -% Choose mode (deterministic/stochastic) - -mode = 1; % 0 - deterministic aproach - % 1 - simulated annealing approach - % Please note that when using simulated annealing approach you - % may need to perform some tuning before use. - -%[h1, fit1, e1, param] = Fit_Logit_allstim(tc,Run,t,mode); -V0 = [ 1 6 1 0.5 10 1 15]; -[VM, h1, fit1, e1, param] = Fit_Logit_allstim(V0,tc,TR, Runc, T); -[pv sres sres_ns1] = ResidScan(e1, FWHM); -[PowLoss1] = PowerLoss(e1, fit1, (len-7) , tc, TR, Runc, alpha); - -hold on; han(2) = plot(fit1,'r'); - -disp('Summary: IL_function'); - -disp('Amplitude:'); disp(param(1)); -disp('Time-to-peak:'); disp(param(2)); -disp('Width:'); disp(param(3)); - -disp('MSE:'); disp((1/(len-1)*sum(e1.^2))); -disp('Mis-modeling:'); disp(pv); -disp('Power Loss:'); disp(PowLoss1); - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Fit HRF using FIR-model - -% Choose mode (FIR/sFIR) - -mode = 1; % 0 - FIR - % 1 - smooth FIR - -[h2, fit2, e2, param] = Fit_sFIR(tc,TR,Runc,T,mode); -[pv sres sres_ns2] = ResidScan(e2, FWHM); -[PowLoss2] = PowerLoss(e2, fit2, (len-T) , tc, TR, Runc, alpha); - -hold on; han(3) = plot(fit2,'g'); - -disp('Summary: FIR'); - -disp('Amplitude'); disp(param(1)); -disp('Time-to-peak'); disp(param(2)); -disp('Width'); disp(param(3)); - -disp('MSE:'); disp((1/(len-1)*sum(e2.^2))); -disp('Mis-modeling'); disp(pv); -disp('Power Loss:'); disp(PowLoss2); - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Fit HRF using Canonical HRF + 2 derivatives - -p=1; - -[h3, fit3, e3, param, info] = Fit_Canonical_HRF(tc,TR,Runc,T,p); -[pv sres sres_ns3] = ResidScan(e3, FWHM); -[PowLoss3] = PowerLoss(e3, fit3, (len-p) , tc, TR, Runc, alpha); - -hold on; han(4) = plot(fit3,'m'); - -legend(han,{'Data' 'IL' 'sFIR' 'DD'}) - - -disp('Summary: Canonical + 2 derivatives'); - -disp('Amplitude'); disp(param(1)); -disp('Time-to-peak'); disp(param(2)); -disp('Width'); disp(param(3)); - -disp('MSE:'); disp((1/(len-1)*sum(e3.^2))); -disp('Mis-modeling'); disp(pv); -disp('Power Loss:'); disp(PowLoss3); - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%figure; - - -subplot(3,2,5); -han2 = plot(h1,'r'); -hold on; han2(2) = plot(h2,'g'); -hold on; han2(3) = plot(h3,'m'); -legend(han2,{'IL' 'sFIR' 'DD'}) -title('Estimated HRF'); - - -subplot(3,1,2); hold on; -hh = plot_onsets(R,'k',-3,1); -drawnow - -han3 = plot(sres_ns1,'r'); -hold on; han3(2) = plot(sres_ns2,'g'); -hold on; han3(3) = plot(sres_ns3,'m'); -hold on; plot((1:len),zeros(len,1),'--k'); -legend(han3,{'IL' 'sFIR' 'DD'}) -title('Mis-modeling (time course)'); - - -subplot(3,2,6); hold on; - -[s1] = Fit_sFIR(sres_ns1,TR,Runc,T,0); -[s2] = Fit_sFIR(sres_ns2,TR,Runc,T,0); -[s3] = Fit_sFIR(sres_ns3,TR,Runc,T,0); - -han4 = plot(s1(1:T),'r'); -hold on; han4(2) = plot(s2(1:T),'g'); -hold on; han4(3) = plot(s3(1:T),'m'); -hold on; plot((1:T),zeros(T,1),'--k'); -legend(han4,{'IL' 'sFIR' 'DD'}) -title('Mis-modeling (HRF)'); diff --git a/CanlabCore/Image_computation_tools/image_eval_function.asv b/CanlabCore/Image_computation_tools/image_eval_function.asv deleted file mode 100644 index dd6807d8..00000000 --- a/CanlabCore/Image_computation_tools/image_eval_function.asv +++ /dev/null @@ -1,625 +0,0 @@ -function varargout = image_eval_function(imageNames, fhandle, varargin) -% Evaluate any function, defined by the function handle fhandle, -% on each in-mask voxel for a set of images. -% -% :Usage: -% :: -% -% varargout = image_eval_function(imgnames, fhandle, ['mask', mask], ['preprochandle', preprochandle], varargin) -% -% :Other Optional args: 'outimagelabels' , 'connames' -% :: -% -% varargout = image_eval_function(Y, fhandle, varargin) -% -% -% evaluate fhandle on paired columns of X and Y -% -% :Note: You must call image_eval_function with outputs, one output for -% each output you're requesting from the voxel-level function. -% Eg: -% :: -% [t, df, betaorcontrast, Phi, sigma, stebeta, F, pvals] = ... -% image_eval_function(imgs, fhandle, 'mask', maskimg, 'outimagelabels' , names); -% -% :Inputs: -% -% **fhandle:** -% is a function handle: -% :: -% fhandle = @(variable inputs) fit_gls(variable_input, fixed_inputs); -% -% fhandle = @(y) fit_gls(y, X, c, p, PX); -% -% -% **'outimagelabels':** -% should be followed by a set of image names, one name -% per output of fhandle per element. e.g., outnames{j}{i} is the output -% image for output j and element (input image) i. elements may be images for each -% subject, if a set of one image per subject is entered, or something -% else depending on the nature of input imgs. -% -% Note: do not include suffixes: no .img -% -% :Examples: -% -% Generalized least squares fitting on 100 Y-variables, same X -% :: -% -% % Get image list -% imgs = filenames('trial_height*img', 'char') -% imgs = sort_image_filenames(imgs) -% -% % Get pre-stored design matrix -% X = eventdesign{3}; -% -% preprochandle = @(y) trimts(y, 3, []); -% -% Generate an image with the number of in-analysis (valid) -% subjects in each voxel -% -% EXPT.SNPM.P{2} is a list of subject-level contrast images. -% :: -% -% fhan = @(y) sum(abs(y) > 0 & ~isnan(y)); -% y = image_eval_function(EXPT.SNPM.P{2}, fhan, 'mask', EXPT.mask, 'outimagelabels', {{'sum_valid_antic.img'}}); -% -% y = rand(100, 100); X = y + rand(100, 1); X(:,end+1) = 1; c = [1 0]'; p = 2; PX = pinv(X); -% fhandle = @(y) fit_gls(y, X, c, p, PX); -% [t, df, beta, Phi, sigma, stebeta, F] = fhandle(y); -% -% .. -% tor wager, jan 31, 2007 -% .. - - % setup inputs - % Define shared variables - nout = nargout; - connames = []; - outimagelabels = []; - preprochandle = []; - mask = []; - fstr = []; - nimgs_this_output = []; % - setup_inputs(nout); - - % test data: Use to check and define outputs before running - % --------------------------------------------------------- - disp('Evaluating test data to get output structure.'); - ytest = randn(size(imageNames, 1), 1); - if ~isempty(preprochandle), ytest = preprochandle(ytest); end - - % must hard-code outputs; not ideal... - out1 = []; out2 = []; out3 = []; out4 = []; out5 = []; out6 = []; out7 = []; out8 = []; out9 = []; out10 = []; - out11 = []; out12 = []; out13 = []; out14 = []; out15 = []; out16 = []; out17 = []; out18 = []; out19 = []; out20 = []; - - % outputs are called out1, out2, ... etc. - build_test_eval_string(nargout); - eval(fstr); - - % put all outputs together in cell array - outputs = []; - for i = 1:nout - eval(['outputs{i} = out' num2str(i) ';']); - end - - % get names of images - [outimagenames, nimgs_this_output] = define_output_names(outimagelabels, nout, outputs); - - % Create or get memory-mapped volumes for output images - % Vout is the spm_vol structure for each ouput image - % --------------------------------------------------------------------- - maskInfo = iimg_read_img(imageNames(1,:), 2); - - - - %This doesn't seem to be necessary. iimg_reconstruct_vols does it. - %Vout = create_output_images(maskInfo, outimagenames, nimgs_this_output); - - - fprintf('Outputs: %3.0f images\n', nout); - for ii = 1:nout - if ~isempty(outimagenames{ii}) - fprintf(' %s \t- %3.0f volume(s)\n', outimagenames{ii}(1,:), nimgs_this_output(ii)); - end - end - fprintf('\n'); - - % Prepare data matrix - % --------------------------------------------------------------------- - fprintf('Loading data... ') - tic - [Y, maskInfo] = iimg_get_data(mask, imageNames); - fprintf(' %3.0f sec\n', toc); - - - - % perform preprocessing function at each voxel - % --------------------------------------------------------------------- - [~, v] = size(Y); - if ~isempty(preprochandle) - fprintf('Preprocessing:\n') - disp(preprochandle); - updateiterations = 1:round(v ./ 100):v; - updateperc = round(linspace(0, 100, length(updateiterations))); - fprintf('Working ...') - - for i = 1:v - Y(:,i) = preprochandle(Y(:,i)); - - update = ( updateiterations == i ); - if any(update), fprintf('\b\b\b\b%3d%%', updateperc(update)); end - end - - fprintf(' Done. \n') - end - - fprintf('Running analysis on function: ') - disp(fhandle); - - - % fit function at each voxel - % --------------------------------------------------------------------- - - % build string (fstr) that defines outputs - build_eval_string(nargout); - - % must hard-code outputs; not ideal... - out1 = []; out2 = []; out3 = []; out4 = []; out5 = []; out6 = []; out7 = []; out8 = []; out9 = []; out10 = []; - - % outputs are called out1, out2, ... etc. - eval(fstr); - - % put all outputs together in cell array - outputs = []; - for i = 1:nargout - eval(['outputs{i} = out' num2str(i) ';']); - end - - % clear outputs to save memory - clear out1 out2 out3 out4 out5 out6 out7 out8 out9 out10 - - % Example of what this might look like for a specific application: - %[t, df, beta, Phi, sigma, stebeta, F] = matrix_eval_function(Y, fhandle); - % - % Evaluated at each voxel: fhandle = @(y) fit_gls(y, X, c, p, PX); - - - - % reconstruct images and write - % --------------------------------------------------------------------- - - - - % write them - fprintf('Writing output images... '); - tic - - write_output_images; - - fprintf(' %3.0f sec\n', toc); - - varargout = outputs; - - %END OF MAIN FUNCTION CODE - - - % ------------------------------------------------------------------- - % - % - % INLINE FUNCTIONS - % - % - % - % ------------------------------------------------------------------- - - function setup_inputs(nout) - mask = []; - preprochandle = []; - outimagelabels = cell(1, nout); - connames = {}; - startslice = 1; - - for i = 1:length(varargin) - if ischar(varargin{i}) - switch varargin{i} - - % functional commands - case 'mask', mask = varargin{i+1}; varargin{i+1} = []; - case 'data', dat = varargin{i+1}; varargin{i+1} = []; - case 'preprochandle', preprochandle = varargin{i+1}; varargin{i+1} = []; - - case {'outnames', 'outimagenames', 'outimagelabels'}, outimagelabels = varargin{i+1}; varargin{i+1} = []; - case 'connames', connames = varargin{i+1}; varargin{i+1} = []; - - case {'start', 'startslice', 'slice'}, startslice = varargin{i+1}; - - otherwise, warning(['Unknown input string option:' varargin{i}]); - end - end - end - - if isempty(mask), error('mask is empty. no-mask opt. not implemented yet.'); - else fprintf('Found mask: %s\n', mask); - end - - if nout ~= length(outimagelabels) - disp('Warning! Length of outimagelabels does not match number of outputs requested.'); - end - - imageNames = char(imageNames); - end - - % % ------------------------------------------------------------------- - % % build string to evaluate that defines outputs and inputs - % % ------------------------------------------------------------------- - function build_eval_string(numargs) - fstr = '['; - for arg = 1:numargs - fstr = [fstr 'out' num2str(arg)]; - if arg ~= numargs - fstr = [fstr ', ']; - else - fstr = [fstr '] = matrix_eval_function(Y, fhandle);']; - end - end - end - - function build_test_eval_string(numargs) - fstr = '['; - for arg = 1:numargs - fstr = [fstr 'out' num2str(arg)]; - if arg ~= numargs - fstr = [fstr ', ']; - else - fstr = [fstr '] = fhandle(ytest);']; - end - end - end - - % % ------------------------------------------------------------------- - % % print banner and define update points for text output - % % ------------------------------------------------------------------- - function print_banner() - fprintf('matrix_eval_function.m') - fprintf('\n_______________________________\n') - fprintf('Evaluating this function on %3.0f variables:\n', v); - disp(fhandle); - fprintf('...using this command:\n%s\n', fstr); - fprintf('_______________________________\n') - str = sprintf('Running ... Done %03d%%', 0); fprintf(str); - updateiterations = 1:round(v ./ 100):v; - updateperc = round(linspace(0, 100, length(updateiterations))); - end - - - % % ------------------------------------------------------------------- - % % Define image output names - % % ------------------------------------------------------------------- - % % % function define_output_names - % % % - % % % isok = []; - % % % % enter full set of output image names in outputimagelabels{i}{j} - % % % % check and see if conditions are met: - % % % for i = 1:nout - % % % if length(outimagelabels) >= i - % % % if length(outimagelabels{i}) == size(outputs{i}, 2) - % % % - % % % for j = 1 : length(outimagelabels{i}) - % % % if ischar(outimagelabels{i}{j}) - % % % isok(i, j) = 1; - % % % end - % % % end - % % % end - % % % end - % % % end - % % % - % % % % if all is good, do nothing further; else, create - % % % if ~isempty(isok) && all(isok(:)) - % % % disp('Using input image labels as-is'); - % % % outimagenames = outimagelabels; - % % % return - % % % - % % % else - % % % disp('Attempting to create output image names.'); - % % % end - % % % - % % % % enter stem for output names for each output in - % % % % outputimagelabels{x}, and names for each element - % % % % (subject/contrast/etc) image within each output in connames - % % % % or no connames for numbers - % % % - % % % if ~exist('connames', 'var'), connames = []; end - % % % nout = length(outputs); - % % % - % % % nc = length(connames); - % % % - % % % outimagenames = cell(1, nout); - % % % for i = 1:length(outputs) - % % % % same number of output params as connnames? - % % % if nc == size(outputs{i}, 2) - % % % for j = 1:nc - % % % outimagenames{i}{j} = [outimagelabels{i} '_' connames{j} '.img']; - % % % end - % % % elseif size(outputs{i}, 2) == 1 - % % % % only one, don't number - % % % outimagenames{i}{1} = [outimagelabels{i} '.img']; - % % % else - % % % for j = 1:size(outputs{i}, 2) - % % % outimagenames{i}{j} = [outimagelabels{i} num2str(j) '.img']; - % % % end - % % % end - % % % end - % % % - % % % nimgs_this_output = ones(1, nout); - % % % - % % % for ii = 1:nout - % % % nimgs_this_output(ii) = numel(outputs{ii}); - % % % end - % % % - % % % end - - - - - % % ------------------------------------------------------------------- - % % Write to disk - % % ------------------------------------------------------------------- - function write_output_images - nout = length(outputs); - nc = length(connames); - for i = 1:nout - nimgs = size(outputs{i}, 2); - if nimgs > 1, fprintf(' %03d', 0); end - - for j = 1:nimgs % changed output file names from previous cell array to list - - if nimgs > 1, fprintf('\b\b\b\b %03d', j); end - - % Note: 12/5/2007; with SPM2 at least, the range of a - % multi-volume image seems to be clipped for each image to - % the range of the first volume when the image is created. - % Therefore, it is important to create the images with the - % appropriate range! - % has something to do with spm scaling factor: - % pinfo scale and offset seem to be set based on first - % volume only. creating first using spm_create_vol may not - % help...? - % if multiple images, re-create first image with range of - % data across images... - % kludgy fix... - % Note: 4/1/08: This was an SPM scaling factor issue. - % Images are now created by iimg_reconstruct_vols with - % scaling factors of intercept = 0, slope = 1 - - if nimgs > 1 && j == 1 - % % % %mymax = max(abs(outputs{i}(:))); % do the below to - % % % %avoid out of memory errors - % % % mymax = max( max(max(outputs{i})), abs(min(min(outputs{i}))) ); - tmp = outputs{i}(:,j)'; - % % % tmp(1) = mymax; tmp(2) = -mymax; - iimg_reconstruct_vols(tmp', maskInfo, 'outname', deblank(outimagenames{i}(j,:))); - - % code for checking stuff: - %i, j, tmp = outputs{i}(:,j)'; create_figure('plot'); plot(tmp), spm_image('init', deblank(outimagenames{i}(j,:))), global VV, VV = spm_vol(outimagenames{i}(j,:)); spm_type(VV.dim(4)) , spm_type(VV.private.hdr.dime.datatype), global vv, vv = spm_read_vols(VV); min(vv(:)), max(vv(:)) - else - iimg_reconstruct_vols(outputs{i}(:,j), maskInfo, 'outname', deblank(outimagenames{i}(j,:))); - end - - - % re-write first image, if needed - if nimgs > 1 && j == nimgs - iimg_reconstruct_vols(outputs{i}(:,1), maskInfo, 'outname', deblank(outimagenames{i}(1,:))); - end - end - end - end -end % main function - - - - - -% % ------------------------------------------------------------------- -% % Define output image names -% % ------------------------------------------------------------------- - - function [outimagenames, nimgs_this_output] = define_output_names(outimagelabels, nout, outputs) - - for i = 1:nout - if length(outimagelabels) >= i % if we have an output image name for this output - outnm = outimagelabels{i}; - - % Make sure we have .img extensions - for j = 1:size(outnm, 1) % for each name in this set, if there is more than one - this_img = deblank(outnm(j, :)); - - if ~strcmp(this_img(end-3:end), '.img') && ~strcmp(this_img(end-3:end), '.nii') - % we need to add img extenstion - error('You must add .img to the end of each name.'); - - %outnm(j, :) = [outnm(j, :) '.img']; - end - end - - %outimagenames{i} = outnm; - end - end - - nimgs_this_output = ones(1, nout); - - for ii = 1:nout - nimgs_this_output(ii) = numel(outputs{ii}); - end - - for ii = 1:nout - if nimgs_this_output(ii) > 1 - % if long enough, leave it alone - if size(outimagelabels{ii}, 1) == nimgs_this_output(ii) - % we're OK, leave the names alone - outimagenames{ii} = outimagelabels{ii}; - - elseif size(outimagelabels{ii}, 1) == 1 - - % we have single name, but multiple output vols requested; expand into multi-volume set - spm_imgs = expand_4d_filenames(outimagelabels{ii}, nimgs_this_output(ii)); - - outimagenames{ii} = spm_imgs; - - else - % something is wrong. - disp('Image names and requested output lengths do not match up.') - disp('Either enter a string matrix defining image names for each element of each output,') - disp('or a single image name for the 4-D image for multiple-element outputs.'); - error('Quitting.'); - - end - else - outimagenames{ii} = outimagelabels{ii}; - end - end - end - - - - - - -% % ------------------------------------------------------------------- -% % Create new output images or check for existing ones -% % ------------------------------------------------------------------- - -function Vout = create_output_images(maskInfo, outimagenames, nimgs_this_output) - - Vout = {}; - - for i = 1:length(outimagenames) - - if size(outimagenames{i}, 1) == nimgs_this_output(i) - % One named image per output volume - elseif size(outimagenames{i}, 1) == 1 - - else - % Mismatch! - error('There seems to be an image name / output number mismatch.') - end - - fprintf('Output %3.0f : %3.0f output volumes ', i, nimgs_this_output(i)) - - for j = 1:size(outimagenames{i}, 1) - % Named images or 4-D volumes (in which case, create first frame only) - - % % % % enforce no filename extension; ext of type .img will be added - % % % % below - % % % - % % % [dummy, outimagenames{i}, e] = fileparts(outimagenames{i}(1,:)); - % % % namestr = ['.' filesep outimagenames{i} '.img']; - - namestr = deblank(outimagenames{i}(j, :)); - - if exist(namestr, 'file') - % image already exists; add to current - fprintf(' ...Found existing: %s\n', namestr); - - else - % don't need to create ALL images, cause will write whole-images - % later... - fprintf(' ...Creating: %s\n', namestr); - - Vout{i}{j} = make_output_image(maskInfo, namestr, ' ', 1); % single-volume only - end - end - end - -end - -% % Create one output image -% % ------------------------------------------------------------------- -function V = make_output_image(maskInfo, fname, descrip, n) - V = struct('fname', '', 'dim', maskInfo.dim, 'mat', maskInfo.mat, 'pinfo', maskInfo.pinfo); - - %V.dim(4) = spm_type(Type); - - % set data type to float - switch(spm('Ver')) - case 'SPM2' - Type = 'float'; %'double'; - V.dim(4) = spm_type(Type); - case 'SPM5' - Type = 'float32'; - V.dt(1) = spm_type(Type); % NOT TESTED. %'float32'); - V.dt(2) = maskInfo.dt(2); - otherwise - error('Unknown SPM version "%s": neuroscientists of the future, fix me!', spm('Ver')); - end - - V.fname = fname; - V.descrip = descrip; - V.n = n; - spm_create_vol(V); - - V.pinfo(1) = 1; % set image scaling factor to 1 - V.pinfo(2) = 0; % set image offset to 0 - V.pinfo(3) = prod(V.dim(1:3)) * spm_type(Type, 'bits') / 8 * (n-1); - - dat = NaN .* zeros(V.dim(1:3)); - spm_write_vol(V, dat); - -end - -% % % .img files can contain multiple volumes (indexed by .n in spm_vol structure). -% % % how many are in this volume? -% % % see also: scn_num_volumes.m -% % % % ------------------------------------------------------------------- -% % function n = Nvol(V) -% % % number of images n stored in this file -% % -% % if ~isstruct(V) -% % V = spm_vol(V); -% % spm_close_vol(V); -% % end -% % -% % fp = fopen(V.fname); -% % fseek(fp, 0, 'eof'); -% % Len = ftell(fp); -% % fclose(fp); -% % -% % switch(spm('Ver')) -% % case 'SPM2' -% % mydt = V.dim(4); -% % case 'SPM5' -% % mydt = V.dt(1); -% % otherwise -% % error('Unknown SPM version "%s": neuroscientists of the future, fix me!', spm('Ver')); -% % end -% % n = Len/(prod(V.dim(1:3))*spm_type(mydt, 'bits')/8); -% % end - -% .img files can contain multiple volumes (indexed by .n in spm_vol structure). -% how many are in this volume? -% % ------------------------------------------------------------------- -function n = Nvol(V) - % number of images n stored in this file - - if ~isstruct(V) - V = spm_vol(V); - %spm_close_vol(V); % spm2 - end - - switch(spm('Ver')) - case 'SPM2' - fp = fopen(V.fname); - fseek(fp,0,'eof'); - Len = ftell(fp); - fclose(fp); - n = Len/(prod(V.dim(1:3))*spm_type(V.dim(4),'bits')/8); - - case 'SPM5' - n = length(V); - - otherwise - error('Unknown SPM version "%s": neuroscientists of the future, fix me!', spm('Ver')); - end - -end - diff --git a/CanlabCore/Image_computation_tools/image_eval_function.m b/CanlabCore/Image_computation_tools/image_eval_function.m index 286bed42..068329f1 100644 --- a/CanlabCore/Image_computation_tools/image_eval_function.m +++ b/CanlabCore/Image_computation_tools/image_eval_function.m @@ -544,16 +544,15 @@ function print_banner() %V.dim(4) = spm_type(Type); % set data type to float - switch(spm('Ver')) + switch spm('Ver') case 'SPM2' Type = 'float'; %'double'; V.dim(4) = spm_type(Type); - case 'SPM5' + otherwise + % SPM5+, including any future versions Type = 'float32'; - V.dt(1) = spm_type(Type); % NOT TESTED. %'float32'); + V.dt(1) = spm_type(Type); V.dt(2) = maskInfo.dt(2); - otherwise - error('Unknown SPM version "%s": neuroscientists of the future, fix me!', spm('Ver')); end V.fname = fname; @@ -609,19 +608,16 @@ function print_banner() %spm_close_vol(V); % spm2 end - switch(spm('Ver')) + switch spm('Ver') case 'SPM2' fp = fopen(V.fname); fseek(fp,0,'eof'); Len = ftell(fp); fclose(fp); n = Len/(prod(V.dim(1:3))*spm_type(V.dim(4),'bits')/8); - - case 'SPM5' - n = length(V); - otherwise - error('Unknown SPM version "%s": neuroscientists of the future, fix me!', spm('Ver')); + % SPM5+, including any future versions + n = length(V); end end diff --git a/CanlabCore/Image_computation_tools/image_eval_function_multisubj.m b/CanlabCore/Image_computation_tools/image_eval_function_multisubj.m index 9c0d1045..90eba818 100644 --- a/CanlabCore/Image_computation_tools/image_eval_function_multisubj.m +++ b/CanlabCore/Image_computation_tools/image_eval_function_multisubj.m @@ -73,16 +73,10 @@ function image_eval_function_multisubj(imageNames, fhandle, varargin) switch spm('Ver') case 'SPM2' - % spm_defaults is a script + % SPM2: spm_defaults is a script, not callable here disp('WARNING: spm defaults not set for spm2. Make sure your defaults are set correctly'); - - case {'SPM5', 'SPM8', 'SPM12'} - % spm_defaults is a function - spm_defaults() - otherwise - % unknown SPM - disp('Unknown version of SPM!'); + % SPM5+, including any future versions spm_defaults() end @@ -122,16 +116,11 @@ function image_eval_function_multisubj(imageNames, fhandle, varargin) V{ii} = spm_vol(imageNames{ii}); switch lower(spm('Ver')) - case 'spm2' % OK - case {'spm5', 'spm8', 'spm12'} - % update image number - n(ii) = length(V{ii}); - otherwise - error('I don''t recognize your version of SPM!'); - + % SPM5+, including any future versions; update image number + n(ii) = length(V{ii}); end @@ -552,17 +541,15 @@ function image_eval_function_multisubj(imageNames, fhandle, varargin) V = struct('fname','', 'dim', maskInfo.dim, 'mat',maskInfo.mat, 'pinfo', [1 0 maskInfo.pinfo(3, 1)]'); % scaling factors 1 0, keep data type from mask % set data type to float - switch(lower(spm('Ver'))) + switch lower(spm('Ver')) case 'spm2' Type = 'double'; V.dim(4) = spm_type(Type); - - case {'spm5', 'spm8', 'spm12'} + otherwise + % SPM5+, including any future versions Type = 'float32'; - V.dt(1) = spm_type(Type); + V.dt(1) = spm_type(Type); V.dt(2) = 1; - otherwise - error('Unknown SPM version "%s": neuroscientists of the future, fix me!', spm('Ver')); end V.fname = [fname '.img']; @@ -611,19 +598,16 @@ function image_eval_function_multisubj(imageNames, fhandle, varargin) %spm_close_vol(V); % spm2 end - switch(lower(spm('Ver'))) + switch lower(spm('Ver')) case 'spm2' fp = fopen(V.fname); fseek(fp,0,'eof'); Len = ftell(fp); fclose(fp); n = Len/(prod(V.dim(1:3))*spm_type(V.dim(4),'bits')/8); - - case {'spm5', 'spm8', 'spm12'} - n = length(V); - otherwise - error('Unknown SPM version "%s": neuroscientists of the future, fix me!', spm('Ver')); + % SPM5+, including any future versions + n = length(V); end end diff --git a/CanlabCore/Image_computation_tools/mean_image.m b/CanlabCore/Image_computation_tools/mean_image.m index 36634bfc..7f0059fa 100644 --- a/CanlabCore/Image_computation_tools/mean_image.m +++ b/CanlabCore/Image_computation_tools/mean_image.m @@ -99,13 +99,10 @@ switch spm('Ver') case 'SPM2' - % spm_defaults is a script + % SPM2: spm_defaults is a script, not callable here disp('WARNING: spm defaults not set for spm2. Make sure your defaults are set correctly'); - - case 'SPM5' - % spm_defaults is a function - spm_defaults() - case 'SPM8' + otherwise + % SPM5+, including any future versions spm_defaults() end diff --git a/CanlabCore/Image_computation_tools/scn_num_volumes.m b/CanlabCore/Image_computation_tools/scn_num_volumes.m index 7d249324..28bffda5 100644 --- a/CanlabCore/Image_computation_tools/scn_num_volumes.m +++ b/CanlabCore/Image_computation_tools/scn_num_volumes.m @@ -56,19 +56,16 @@ %spm_close_vol(V); % spm2 end - switch(spm('Ver')) + switch spm('Ver') case 'SPM2' fp = fopen(V.fname); fseek(fp,0,'eof'); Len = ftell(fp); fclose(fp); n = Len/(prod(V.dim(1:3))*spm_type(V.dim(4),'bits')/8); - - case {'SPM5' 'SPM8' 'SPM12'} - n = length(V); - otherwise - error('Unknown SPM version "%s": neuroscientists of the future, fix me!', spm('Ver')); + % SPM5+, including any future versions + n = length(V); end end diff --git a/CanlabCore/Image_computation_tools/scn_write_plane.m b/CanlabCore/Image_computation_tools/scn_write_plane.m index 95f5c833..81c22360 100644 --- a/CanlabCore/Image_computation_tools/scn_write_plane.m +++ b/CanlabCore/Image_computation_tools/scn_write_plane.m @@ -124,20 +124,13 @@ if isfield(exampleV, 'n'), Vout.n = exampleV.n; end % SPM5 only % Make sure datatype is float / float32 - switch(spm('Ver')) + switch spm('Ver') case 'SPM2' - Vout.dim(4) = spm_type('float'); - - case 'SPM5' - Vout.dt = [spm_type('float32') 0]; - if isfield(exampleV, 'dt'), Vout.dt(2) = exampleV.dt(2); end % use endian-ness of example. - - case 'SPM8' - Vout.dt = [spm_type('float32') 0]; - if isfield(exampleV, 'dt'), Vout.dt(2) = exampleV.dt(2); end % use endian-ness of example. - + Vout.dim(4) = spm_type('float'); otherwise - error('Unknown SPM version.'); + % SPM5+, including any future versions + Vout.dt = [spm_type('float32') 0]; + if isfield(exampleV, 'dt'), Vout.dt(2) = exampleV.dt(2); end % use endian-ness of example. end try @@ -173,15 +166,12 @@ % fprintf('Checking spm_vol structures '); % Make sure spm_vol structure (SPM5 only) % alter data type if needed - switch(lower(spm('Ver'))) + switch lower(spm('Ver')) case 'spm2' V = filenames_or_V; % Skip because hard to check. - case {'spm5', 'spm8', 'spm12'} - V = spm_vol(filenames_or_V); -% otherwise -% error('Fix or verify image writing for SPMxx. Not implemented yet.'); otherwise - error('Unknown SPM version.'); + % SPM5+, including any future versions + V = spm_vol(filenames_or_V); end end @@ -206,14 +196,12 @@ function create_empty(V) % Write empty data -switch(spm('Ver')) +switch spm('Ver') case 'SPM2' spm_write_vol(V, zeros(V.dim(1:3))); - case {'SPM5', 'SPM8'} - % assign directly - for i = 1:length(V), V(i).private.dat(:, :, :) = 0; end otherwise - error('Unknown SPM version.') + % SPM5+, including any future versions: assign directly + for i = 1:length(V), V(i).private.dat(:, :, :) = 0; end end end diff --git a/CanlabCore/Image_computation_tools/spm_htw_from_fit.asv b/CanlabCore/Image_computation_tools/spm_htw_from_fit.asv deleted file mode 100644 index 39ba1748..00000000 --- a/CanlabCore/Image_computation_tools/spm_htw_from_fit.asv +++ /dev/null @@ -1,677 +0,0 @@ -function spm_htw_from_fit(varargin) -% When fitting a 1st-level SPM model with multiple basis functions, -% reconstructs fitted response at each voxel beta images and basis functions, and -% estimates height (amplitude), time-to-peak, and response width (duration and half-max) -% For each condition. Saves maps of these across voxels, and contrasts -% across height, time-to-peak, and width maps if contrasts are specified. -% This provides contrast images to take to 2nd-level analyses for group statistics when -% using multiple basis functions at the 1st level. -% -% :Usage: -% :: -% -% function spm_htw_from_fit(varargin) -% -% :NOTES: -% -% This function loads the SPM.mat file in the current directory and uses -% the basis set specified in the loaded SPM structure. -% -% : Required Inputs: -% None - run within an SPM first-level analysis directory -% -% :Optional Inputs: -% -% **'amplitudes'**: -% do amplitudes only; no contrasts -% -% **'contrasts'**: -% do contrasts only; no amplitudes -% -% **'noamplitudes'** -% skip create amping images (combination of betas -% across basis functions) -% -% **'nocontrasts'** -% skip creation of contrast images -% (running contrast images assumes that amplitude images are already -% created) -% -% **'all'** -% will run both the amplitudes and contrasts sections -% -% - The second way uses CANlab HTW code to estimate height, time to peak, -% width, and area under the curve (see Lindquist & Wager 2007 for -% simulations using a version of this code). -% It requires SCANlab specific functions, in SCN_Core_Support -% (unlike the deriv. boost). -% To turn this OFF, enter 'nohtw' as an optional argument -% -% **'startend'** -% followed by starting and ending values in seconds for amplitude -% estimation window (for HTW estimation only). -% If you do not enter this, it will show you a plot and ask you to pick -% these values. -% If you enter them here as inputs, you can skip the interactive step and -% loop over subjects. -% -% **'condition_numbers'** -% followed by which index numbers in your list should -% be used to calculate h, t, w from. You should use this if you -% are entering regressors of no interest, besides the intercepts. -% -% :Important for Contrasts: -% disp('Using contrasts entered as F-contrasts. Assuming the first contrast vector in each F-contrast ' -% -% disp('is a contrast of interest across the CANONICAL basis function regressors.') -% -% :Output: -% :: -% Reconstructed amplitude, time-to-peak, and duration (width) images for each event type -% Contrasts acrss -% -htw_amplitude_006.nii -beta_0002.nii htw_area_under_curve_001.nii -beta_0003.nii htw_area_under_curve_002.nii -beta_0004.nii htw_area_under_curve_003.nii -beta_0005.nii htw_area_under_curve_004.nii -beta_0006.nii htw_area_under_curve_005.nii -beta_0007.nii htw_area_under_curve_006.nii -beta_0008.nii htw_time_to_peak_001.nii -beta_0009.nii htw_time_to_peak_002.nii -beta_0010.nii htw_time_to_peak_003.nii -beta_0011.nii htw_time_to_peak_004.nii -beta_0012.nii htw_time_to_peak_005.nii -beta_0013.nii htw_time_to_peak_006.nii -beta_0014.nii htw_width_001.nii -con_0001.nii htw_width_002.nii -con_htw_ampl_targetvsstandards5.nii htw_width_003.nii -con_htw_area_targetvsstandards5.nii htw_width_004.nii -con_htw_time_targetvsstandards5.nii htw_width_005.nii -con_htw_widt_targetvsstandards5.nii htw_width_006.nii -db_amplitude_names.mat mask.nii - - -% :Examples: -% :: -% -% % RUN THIS IN COMMAND WINDOW TO BATCH -% subj = dir('06*') -% for i = 1:length(subj), cd(subj(i).name), spm_htw_from_fit, cd('..'); end -% -% % ANOTHER BATCH EXAMPLE: -% d = dir('remi*'); d = d(cat(2, d.isdir)); [mydirs{1:length(d)}] = deal(d.name) -% for i = 1:length(mydirs), cd(mydirs{i}), spm_htw_from_fit('all', 'nodb', 'startend', [4 15]), cd('..'); end -% -% %An example for an event-related design, specifying condition numbers to get HTW from: -% spm_htw_from_fit('all', 'contrasts', 'condition_numbers', 1:14, 'startend', [4 10]); -% -% % CALCULATE CONTRASTS ONLY ON ALREADY-ESTIMATED HTW IMAGES -% spm_htw_from_fit('contrasts','condition_numbers',1:14); -% -% :References: -% :: -% Lindquist, M. A. & Wager, T. D. (2007). Validity and power in hemodynamic response modeling: -% a comparison study and a new approach. Human Brain Mapping. 8:764-84. -% -% Lindquist, M. A., Meh Loh, J., Atlas, L. Y. & Wager T. D. (2009). Modeling the hemodynamic -% response function in fMRI: efficiency, bias and mis-modeling. Neuroimage. 45:187-98. - -% Notes: -% This function is adapted from apply_derivative_boost.m, which was -% originally intended to implement Calhoun derivative boost, but this is -% now deprecated. -% Original documentation: -% In addition, 'amplitudes' now has two separate parts: -% - The first uses Vince Calhoun's derivative boost (Calhoun, 2004) to -% estimate amplitudes. NOTE: *We have not worked out the scaling yet, so -% I'm not sure this is working right* -% To turn this OFF, enter 'nodb' as an optional argument -% - - spmname = fullfile(pwd, 'SPM.mat'); % SETUP INPUTS - if ~exist(spmname, 'file'), error('You must be in an SPM 1st-level results directory with SPM5 SPM.mat file.'); end - - load(spmname); - - nbf = size(SPM.xBF.bf, 2); - - docontrasts = 1; - doamps = 1; - nodb = 1; - nohtw = 0; - condition_numbers = []; - do_downsample = []; % downsample; default = 1 sec if units are seconds (1/dt) - - for i = 1:length(varargin) - if ischar(varargin{i}) - switch varargin{i} - % reserved keywords - case 'all', docontrasts = 1; doamps = 1; - - case 'amplitudes', doamps = 1; docontrasts = 0; - - case 'contrasts', docontrasts = 1; doamps = 0; - - case 'noamplitudes', doamps = 0; - - case 'nocontrasts', docontrasts = 0; - - case 'db', nodb = 0; % original DB estimation (legacy, deprecated) - - case 'nohtw', nohtw = 1; % skip HTW estimation - - case 'startend', startend = varargin{i + 1}; % starting and ending values in seconds for amplitude estimation window (for HTW estimation only). - - case 'condition_numbers', condition_numbers = varargin{i + 1}; - - case 'nodownsample', do_downsample = 0; - - case 'downsample', do_downsample = varargin{i + 1}; - - otherwise, warning(['Unknown input string option:' varargin{i}]); - end - end - end - - if isempty(do_downsample) - % default downsampling - do_downsample = round(1 ./ SPM.xBF.dt); - end - - if ~(docontrasts || doamps) - disp('Nothing to do! Enter ''contrasts'' ''amplitudes'' or ''all'' as input argument.'); - return - end - - - % --------------------------------------------- - % FILE NAMES and imgtype - % --------------------------------------------- - - imgs = dir(sprintf(['beta*img'])); imgs = char(imgs.name); - - if isempty(imgs) - imgs = dir(sprintf(['beta*nii'])); imgs = char(imgs.name); - imgtype = '.nii'; - else - imgtype = '.img'; - end - - if isempty(imgs) - error('Cannot find beta*img or beta*nii files in current folder') - end - - n = size(imgs, 1); - - - if doamps - % --------------------------------------------- - % --------------------------------------------- - - % ESTIMATE AMPLITUDES - - % --------------------------------------------- - % --------------------------------------------- - - - - - fprintf('Found %3.0f beta images', n); fprintf('\n'); - - %load(spmname); - nsess = length(SPM.Sess); - fprintf('I think there are %3.0f sessions (runs)', nsess); fprintf('\n'); - - if ~isempty(condition_numbers) - wh_intercept = true(1, size(imgs, 1)); % exclude these - wh_intercept(condition_numbers) = 0; - wh_intercept = find(wh_intercept); - - fprintf('\nIncluding only these images: '); - fprintf('%3.0f ', condition_numbers); - fprintf('\n'); - - else - wh_intercept = SPM.xX.iB; - fprintf('\nI think these images are intercepts, and am not using them: '); - fprintf('%3.0f ', wh_intercept); - fprintf('\n'); - end - - imgs(wh_intercept, :) = []; - - %not used; only when using image_eval_function - %mask_img = './mask.img'; - - n = size(imgs, 1); - - - if n / nbf ~= round(n / nbf), error('Error! Wrong number of images for the specified number of basis functions.'); end - - - - if nodb - % skip Derivative Boost estimation and go straight to HTW - else - % DO DB - - switch nbf - case 2 - derivative_case = 'timeonly'; - case 3 - derivative_case = 'timedispersion'; - - otherwise - warning('Deriv. Boost only works for SPM canonical hrf with time or time + dispersion derivatives. This SPM.mat doesn''t match those specs.'); - end - - % Not used; only for image eval function - % boost = @(b) sign(b(1)) .* sqrt(sum(b .^ 2)); - - - - % --------------------------------------------- - % CALCULATE - % --------------------------------------------- - cond_indx = 1; - - for i = 1: nbf : (n - nbf + 1) - - imgs_cond = imgs(i : i+nbf - 1, :); - - disp('Working on :') - disp(imgs_cond) - - out_name = sprintf(['db_amplitude_%03d',imgtype ], cond_indx); - - % This code uses SCN lab tools to create images - % --------------------------------------------- - % % y = image_eval_function(imgs_cond, boost, 'mask', mask_img, ... - % % 'outimagelabels', {out_name}); - % --------------------------------------------- - - % This code uses SPM instead - % --------------------------------------------- - switch derivative_case - case 'timeonly' - spm_imcalc(imgs_cond, out_name, 'sign(i1) .* sqrt(i1.^2 + i2.^2)'); - case 'timedispersion' - spm_imcalc(imgs_cond, out_name, 'sign(i1) .* sqrt(i1.^2 + i2.^2 + i3.^2)'); - otherwise - error('Basis set is incompatible with DB estimation!'); - end - - fprintf('Created %s\n', out_name); - % --------------------------------------------- - - cond_indx = cond_indx + 1; - end - - % Get and save names - db_amp_names = []; - for i = 1:nsess - sessnames = char(SPM.Sess(i).Fc.name); - sessnames = [repmat(sprintf('Sess%02d_', i), size(sessnames, 1), 1) sessnames]; - db_amp_names = strvcat(db_amp_names, sessnames); - end - - save db_amplitude_names db_amp_names - disp(db_amp_names); - disp(' ') - disp('Saved DB amplitude condition names for each image in db_amplitude_names.mat'); - - fprintf('\n*-----------------------------*\nApplied DB successfully\n*-----------------------------*\n') - - end - - if nohtw - % skip this - - else - - % --------------------------------------------- - % Estimated amplitude from fit: HTW - % --------------------------------------------- - % This code uses SCN lab tools to create images - - disp(' ') - disp('Next: Estimating amplitude, time to peak, width, and area-under-curve images from fitted response using SCN lab code.') - disp(' ') - - % downsample bf, if requested - if do_downsample - mytimeres = SPM.xBF.dt * do_downsample; - SPM.xBF.bf = downsample(SPM.xBF.bf, do_downsample); - - else - mytimeres = SPM.xBF.dt; - end - - if exist('startend', 'var') - % just check, and use input values - if length(startend) ~= 2, error('Startend input must have two values, a starting and ending value in seconds for the amp. estimate window'), end - - else - % Set range in sec - htw_from_fit(SPM.xBF.bf, ones(size(SPM.xBF.bf, 2), 1), mytimeres, 'plot', 'verbose'); - - disp(' ') - disp('Enter the range in seconds within which to estimate peak amplitude.') - disp('Example: type [3 12] and press return for a typical event-related setup.'); - disp('More sustained responses, like pain responses, may require a longer window.'); - disp('This estimates the amplitude of the IMPULSE RESPONSE, before convolution with the stimulus function') - disp('so if you have an epoch design, a typical window of [3 12] sec is still appropriate.'); - disp('Also note: AUC images are calculated as the area under the curve within the window you specify.') - disp(' ') - disp('In the future, you can input ''startend'', [3 12] (for example) to skip interactive selection') - startend = input('Enter your choice in [ ] and press return: '); - end - - % Test your choice by showing you a plot - htwfunction = @(b) htw_from_fit(SPM.xBF.bf, b, mytimeres, 'startval', startend(1), 'endval', startend(2), 'plot', 'verbose'); - htwfunction(ones(size(SPM.xBF.bf, 2), 1)) - drawnow - - % Create without plot option for loop through brain. - htwfunction = @(b) htw_from_fit(SPM.xBF.bf, b, mytimeres, 'startval', startend(1), 'endval', startend(2)); - - disp('Check the screen for a plot of your choice of window.') - disp(' ') - - - % --------------------------------------------- - % CALCULATE - % --------------------------------------------- - cond_indx = 1; - - for i = 1: nbf : (n - nbf + 1) - - imgs_cond = imgs(i : i+nbf - 1, :); - - disp('Working on :') - disp(imgs_cond) - - clear out_name - out_name{1} = sprintf(['htw_amplitude_%03d',imgtype], cond_indx); - out_name{2} = sprintf(['htw_time_to_peak_%03d',imgtype], cond_indx); - out_name{3} = sprintf(['htw_width_%03d',imgtype], cond_indx); - out_name{4} = sprintf(['htw_area_under_curve_%03d',imgtype], cond_indx); - - % --------------------------------------------- - [h, t, w, auc] = image_eval_function(imgs_cond, htwfunction, 'mask', fullfile(pwd, ['mask', imgtype]), ... - 'outimagelabels', out_name); - - h;t;w;auc; % we need the outputs above to tell it to write 4 images. - % --------------------------------------------- - - cond_indx = cond_indx + 1; - end - - % Get and save names - htw_amp_names = []; - for i = 1:nsess - sessnames = char(SPM.Sess(i).Fc.name); - sessnames = [repmat(sprintf('Sess%02d_', i), size(sessnames, 1), 1) sessnames]; - htw_amp_names = strvcat(htw_amp_names, sessnames); - end - - if ~exist(fullfile(pwd, 'db_amplitude_names.mat'), 'file') - save db_amplitude_names htw_amp_names - else - save db_amplitude_names -append htw_amp_names - end - disp(htw_amp_names); - disp(' ') - disp('Saved HTW amplitude condition names for each image in db_amplitude_names.mat'); - - fprintf('\n*-----------------------------*\nApplied HTW estimation successfully\n*-----------------------------*\n') - - end - - end % amplitudes - - - - % --------------------------------------------- - % --------------------------------------------- - - % CREATE CONTRAST FOR THIS SUBJECT - - % --------------------------------------------- - % --------------------------------------------- - - if docontrasts - - % Load contrast vectors - - % ----------------------------------------- - %load(spmname); - % nsess = length(SPM.Sess); - - % Define which indices to exclude from contrasts - if ~isempty(condition_numbers) - nconvals = length(SPM.xCon(1).c(:, 1)); % test contrast - wh_intercept = true(1, nconvals); % exclude these - wh_intercept(condition_numbers) = 0; - wh_intercept = find(wh_intercept); - - fprintf('\nIncluding only these images: '); - fprintf('%3.0f ', condition_numbers); - fprintf('\n'); - - else - wh_intercept = SPM.xX.iB; - fprintf('\nI think these images are intercepts, and am not using them: '); - fprintf('%3.0f ', wh_intercept); - fprintf('\n'); - end - - - if ~isfield(SPM, 'xCon') - error('Enter F-contrasts first, with the first contrast vector in each F-contrast the contrast across the CANONICAL basis function.'); - else - disp('Using contrasts entered as F-contrasts. Assuming the first contrast vector in each F-contrast ') - disp('is a contrast of interest across the CANONICAL basis function regressors.') - end - - wh_F = strmatch('F', char(SPM.xCon.STAT), 'exact'); - - wh_T = strmatch('T', char(SPM.xCon.STAT), 'exact'); - - wh_F = [wh_F wh_T]; - - if isempty(wh_F) && isempty(wh_T), error('No F-contrasts or T-contrasts entered yet.'); end - - % All sets of contrast images - % ------------------------------------------ - if ~nodb - - ampimgs_name = sprintf(['db_amplitude_*',imgtype]); - - ampimgs = dir(ampimgs_name); ampimgs = char(ampimgs.name); - - if ~isempty(ampimgs) - contrast_image_names_dbamp = calc_contrasts(ampimgs_name, ampimgs, wh_F, SPM, wh_intercept, nbf, imgtype); - end - - end - - - % HTW amplitude - % ------------------------------------------ - ampimgs_name = sprintf(['htw_amplitude_*',imgtype]); - - ampimgs = dir(ampimgs_name); ampimgs = char(ampimgs.name); - - if ~isempty(ampimgs) - contrast_image_names_htwamp = calc_contrasts(ampimgs_name, ampimgs, wh_F, SPM, wh_intercept, nbf, imgtype); - else - disp(['Checked for but did not find: ' ampimgs_name]); - end - - - % HTW time - % ------------------------------------------ - ampimgs_name = sprintf(['htw_time_to_peak_*',imgtype]); - - ampimgs = dir(ampimgs_name); ampimgs = char(ampimgs.name); - - if ~isempty(ampimgs) - contrast_image_names_htwtime = calc_contrasts(ampimgs_name, ampimgs, wh_F, SPM, wh_intercept, nbf, imgtype); - else - disp(['Checked for but did not find: ' ampimgs_name]); - end - - - % HTW width - % ------------------------------------------ - ampimgs_name = sprintf(['htw_width_*',imgtype]); - - ampimgs = dir(ampimgs_name); ampimgs = char(ampimgs.name); - - if ~isempty(ampimgs) - contrast_image_names_htwwid = calc_contrasts(ampimgs_name, ampimgs, wh_F, SPM, wh_intercept, nbf, imgtype); - else - disp(['Checked for but did not find: ' ampimgs_name]); - end - - - % HTW area - % ------------------------------------------ - ampimgs_name = sprintf(['htw_area_under_curve_*',imgtype]); - - ampimgs = dir(ampimgs_name); ampimgs = char(ampimgs.name); - - if ~isempty(ampimgs) - contrast_image_names_htwarea = calc_contrasts(ampimgs_name, ampimgs, wh_F, SPM, wh_intercept, nbf, imgtype); - else - disp(['Checked for but did not find: ' ampimgs_name]); - end - - check_it = whos('contrast_image_names*'); - - if isempty(check_it) - disp('No valid images found to create contrasts on'); - - else - save('db_amplitude_names', '-append', 'contrast_image_names*'); - disp('Saved lists of contrast image names in db_amplitude_names.mat'); - end - - end % end contrasts - - - - - %% INLINE - - - -end % main function - - - - - - - -function contrast_image_names = calc_contrasts(ampimgs_name, ampimgs, wh_F, SPM, wh_intercept, nbf, imgtype) - - - n = size(ampimgs, 1); - - fprintf('Found %3.0f images:\n', n); - disp(ampimgs) - - disp('Reading image data.') - V = spm_vol(ampimgs); - vols = spm_read_vols(V); - - % spm_check_registration(ampimgs); - % colormap jet - - contrast_image_names = []; - - for i = 1:length(wh_F) - - - name = SPM.xCon(wh_F(i)).name; - disp(['Calculating contrast on: ' name]) - original_name = name; - - name = deblank(name); - wh_bad = (name == ' ' | name == ',' | name == '.' | name == '^' | name == '~' | name == '''' | name == ':' | name == '*' | name == '%' | name == ';' | name == '@' | name == '&'); - name(wh_bad) = []; - - name = ['con_', ampimgs_name(1:8), '_', name, imgtype]; - - c = SPM.xCon(wh_F(i)).c(:, 1); - c(wh_intercept) = []; - c = c(1 : nbf : end); - - if length(c) ~= n, error('Contrast is wrong length for some reason! Coding error in this function? Or wrong number of db_amplitude images.'); end - - fprintf('Contrast values: ') - fprintf('%01d ', c) - fprintf('\n') - - % calculate and save - - contrast_image_calc(name, c, vols, V, original_name) - - disp(['Written: ' name]); - - contrast_image_names = strvcat(contrast_image_names, name); - - disp(' '); - - end - - fprintf('\n*-----------------------------*\nContrasts Done successfully!\n*-----------------------------*\n') - spm_check_registration(contrast_image_names); - -end - - - - - -function contrast_image_calc(Q, myc, vols, V, original_name) - - % FROM: - % function contrast_image(Q, myc) - % - % Tor Wager - % - % Creates a contrast image called Q (do not include path) - % Given a list of img files P (spm format, with path) - % and a contrast vector myc - % In the directory of 1st image in P. - - - if ~(length(myc) == size(vols,4)) - error('Contrast vector length is not equal to number of image files.') - end - - myc2 = zeros(size(vols)); - - for i = 1:length(myc) - myc2(:,:,:,i) = myc(i); - end - - cvol = vols .* myc2; - cvol = sum(cvol,4); - - % ------------------------- - % write - % ------------------------- - dd = fileparts(V(1).fname); - Q = fullfile(dd, Q); - Vo = V(1); - Vo.fname = Q; - Vo.descrip = ['Contrast ' original_name]; - - spm_write_vol(Vo,cvol); - -end diff --git a/CanlabCore/Image_computation_tools/tor_global.m b/CanlabCore/Image_computation_tools/tor_global.m index 8c9d3913..ac46d483 100644 --- a/CanlabCore/Image_computation_tools/tor_global.m +++ b/CanlabCore/Image_computation_tools/tor_global.m @@ -39,16 +39,10 @@ switch spm('Ver') case 'SPM2' - % spm_defaults is a script + % SPM2: spm_defaults is a script, not callable here disp('WARNING: spm defaults not set for spm2. Make sure your defaults are set correctly'); - - case {'SPM5', 'SPM8', 'SPM12'} - % spm_defaults is a function - spm_defaults() - otherwise - % unknown SPM - disp('Unknown version of SPM!'); + % SPM5+, including any future versions spm_defaults() end diff --git a/CanlabCore/Index_image_manip_tools/iimg_read_img.m b/CanlabCore/Index_image_manip_tools/iimg_read_img.m index d83dc01b..1d9720cd 100644 --- a/CanlabCore/Index_image_manip_tools/iimg_read_img.m +++ b/CanlabCore/Index_image_manip_tools/iimg_read_img.m @@ -222,16 +222,10 @@ switch spm('Ver') case 'SPM2' - % spm_defaults is a script + % SPM2: spm_defaults is a script, not callable here disp('WARNING: spm defaults not set for spm2. Make sure your defaults are set correctly'); - - case {'SPM5', 'SPM8', 'SPM12', 'SPM25'} - % spm_defaults is a function - spm_defaults() - otherwise - % unknown SPM - disp('Unknown version of SPM!'); + % SPM5+, including any future versions spm_defaults() end diff --git a/CanlabCore/Index_image_manip_tools/iimg_reconstruct_vols.m b/CanlabCore/Index_image_manip_tools/iimg_reconstruct_vols.m index 3e77c855..25e93445 100644 --- a/CanlabCore/Index_image_manip_tools/iimg_reconstruct_vols.m +++ b/CanlabCore/Index_image_manip_tools/iimg_reconstruct_vols.m @@ -184,7 +184,7 @@ end % alter data type if needed - switch(spm('Ver')) + switch spm('Ver') case 'SPM2' if spm_type(volInfo.dim(4), 'swapped') disp('Swapped datatypes are supported by SPM2 but not SPM5. Un-swapping.'); @@ -194,16 +194,13 @@ volInfo.dim(4) = spm_type('float'); end end - - case {'SPM5', 'SPM8', 'SPM12', 'SPM25'} %added keepdt to retain original dt if requested : luk(ea) - %Outputs float32 by default otherwise keeps original data type - %if 'keepdt' is used as optional input. + otherwise + % SPM5+, including any future versions. + % Outputs float32 by default; pass 'keepdt' as optional input to + % retain original data type (luk(ea)). if isfield(volInfo, 'dt') && spm_type(volInfo.dt(1), 'intt') && ~keepdt volInfo.dt(1) = spm_type('float32'); end - - otherwise - error('Unrecognized SPM type. Please update code or use SPM2/5/8/12!'); end end diff --git a/CanlabCore/Index_image_manip_tools/iimg_reslice.m b/CanlabCore/Index_image_manip_tools/iimg_reslice.m index e7b7a37f..4f23f199 100644 --- a/CanlabCore/Index_image_manip_tools/iimg_reslice.m +++ b/CanlabCore/Index_image_manip_tools/iimg_reslice.m @@ -47,15 +47,13 @@ VO = volInfo2; VO.fname = outname; - switch(spm('Ver')) + switch spm('Ver') case 'SPM2' VO.dim = [volInfo.dim(1:3) volInfo2.dim(4)]; - case {'SPM5', 'SPM8'} + otherwise + % SPM5+, including any future versions VO.dt = volInfo.dt; VO.private.dat.fname = outname; - - otherwise - error('Unknown SPM version "%s": neuroscientists of the future, fix me!', spm('Ver')); end VO.mat = volInfo.mat; diff --git a/CanlabCore/Misc_utilities/documentation_template.m b/CanlabCore/Misc_utilities/documentation_template.m index 61980a9e..c3366642 100644 --- a/CanlabCore/Misc_utilities/documentation_template.m +++ b/CanlabCore/Misc_utilities/documentation_template.m @@ -115,8 +115,61 @@ % Enforce valid variable names in a cell array of strings: Eliminate special characters and leading numbers [variable_names, namewarnings] = format_text_letters_only(variable_names, 'numbers', 'cleanup', 'squeeze', 'underscore_ok'); -% ---------------------------------------------------------------------- +% BELOW IS THE INPUT PARSER TEMPLATE + +% ------------------------------------------------------------------------- % Parse inputs +% ------------------------------------------------------------------------- + +% Parse special command keywords and remove them before inputParser + +doplot = false; +plot_idx = strcmpi(varargin, 'plot'); +if any(plot_idx) % Override: omit 'doplot' key/value pair + doplot = true; + varargin(plot_idx) = []; % remove so inputParser doesn't see it +end + +verbose = true; +verbose_idx = strcmpi(varargin, 'noverbose'); +if any(verbose_idx) + verbose = false; + varargin(verbose_idx) = []; % remove so inputParser doesn't see it +end + +% Use inputParser to parse key/value pairs +% First add obligatory/non-conditional keywords + +p = inputParser; +p.addRequired('obj'); +p.addParameter('similarity_metric','correlation',... + @(x) ismember(x, {'correlation','cosine_similarity','dot_product', ... + 'dice','normalized_absolute_agreement','concordance_correlation', ... + 'standardized_abs_deviation','mean_shift_z','scale_shift_z'})); +p.addParameter('treat_zero_as_data', false, @(x) islogical(x) || isnumeric(x)); +p.addParameter('complete_cases', false, @(x) islogical(x) || isnumeric(x)); + +% Special key/value pairs that we have potentially set with optional keywords +p.addParameter('doplot', doplot, @(x) islogical(x) || isnumeric(x)); +p.addParameter('verbose', verbose, @(x) islogical(x) || isnumeric(x)); + +% process inputs and deal out to variables in workspace +p.parse(obj,varargin{:}); + +ARGS = p.Results; + +% Get all field names in ARGS +fn = fieldnames(ARGS); + +% Loop over fields and assign variables in caller workspace +for i = 1:numel(fn) + assignin('caller', fn{i}, ARGS.(fn{i})); +end + +% END INPUT PARSER TEMPLATE + +% ---------------------------------------------------------------------- +% Parse inputs - older % ---------------------------------------------------------------------- % This 2019 version uses the inputParser object. Older schemes are below. % Note: With this, you can pass in EITHER keyword, value pairs OR a diff --git a/CanlabCore/Misc_utilities/framewise_displacement.m b/CanlabCore/Misc_utilities/framewise_displacement.m index ec01a481..f6c988bc 100644 --- a/CanlabCore/Misc_utilities/framewise_displacement.m +++ b/CanlabCore/Misc_utilities/framewise_displacement.m @@ -1,38 +1,74 @@ function [fwd, mean_fwd, est_outliers, mvmt_mtx_mm] = framewise_displacement(mvmt_mtx, varargin) -% FRAMEWISE_DISPLACEMENT Calculate framewise displacement from 6 movement parameters. +% framewise_displacement Calculate framewise displacement from 6 movement parameters. % -% This function computes the framewise displacement (FD) based on Power et al. (2012, 2019) -% to estimate movement outliers in fMRI data. Framewise displacement quantifies the -% amount of head movement between successive frames using both translation and rotation parameters. +% :Usage: +% :: % -% CAUTION!! Rotations must be 1st 3 columns of mvmt_mtx, then translations +% [fwd, mean_fwd, est_outliers, mvmt_mtx_mm] = framewise_displacement(mvmt_mtx, [optional inputs]) % -% Usage: -% mvmt_file = 'path_to_BIDS_confounds_timeseries.tsv'; -% mvmt_data = importBIDSfile(mvmt_file); % Import data from fMRIprep-style confounds_timeseries.tsv -% mvmt_mtx = [mvmt_data.rot_x, mvmt_data.rot_y, mvmt_data.rot_z, mvmt_data.trans_x, mvmt_data.trans_y, mvmt_data.trans_z]; -% [mvmt_mtx, corr_out, uncorr_out] = framewise_displacement(mvmt_mtx); +% Computes the framewise displacement (FD) based on Power et al. (2012, +% 2019) to estimate movement outliers in fMRI data. Framewise +% displacement quantifies the amount of head movement between +% successive frames using both translation and rotation parameters. % -% Inputs: -% mvmt_mtx - A T x 6 matrix containing rotation (rot_x, rot_y, rot_z) in radians -% and translation (trans_x, trans_y, trans_z) in mm. +% Rotational displacements (columns 1-3) are converted from radians to +% mm of arc length on a sphere of radius 50 mm before being summed +% with the translational displacements. % -% Outputs: -% fwd - T x 1 vector of overall framewise displacement estimates -% mean_fwd - mean framewise displacement -% est_outliers - Logical vector of estimated outliers after correction -% mvmt_mtx_mm - The input matrix with rotation parameters converted to mm. +% CAUTION! Rotations must be the 1st three columns of mvmt_mtx, and +% translations must be columns 4-6. % -% References: -% - Power et al. (2012), "Spurious but systematic correlations in functional connectivity MRI networks arise from subject motion." -% - Power et al. (2019), "Respiration pseudomotion in functional connectivity MRI." +% :References: +% - Power et al. (2012), "Spurious but systematic correlations in +% functional connectivity MRI networks arise from subject motion." +% - Power et al. (2019), "Respiration pseudomotion in functional +% connectivity MRI." +% +% Author: Michael Sun, Ph.D. 10/1/2024. +% +% :Inputs: +% +% **mvmt_mtx:** +% A T x 6 matrix containing rotation (rot_x, rot_y, rot_z) in +% radians and translation (trans_x, trans_y, trans_z) in mm. % % :Optional Inputs: % -% **'thresh'** -% Threshold in mm for determining outliers; Default = 0.5 mm +% **'thresh':** +% Threshold in mm for determining outliers. Default = 0.5 mm. +% +% :Outputs: +% +% **fwd:** +% T x 1 vector of overall framewise displacement estimates (mm). +% The first entry is set to 0 since there is no displacement for +% the first time point. +% +% **mean_fwd:** +% Mean framewise displacement across the run (using nanmean). +% +% **est_outliers:** +% T x 1 logical vector of estimated outliers; true where +% fwd > thresh. +% +% **mvmt_mtx_mm:** +% The input matrix with rotation parameters converted to mm +% (translation columns are unchanged). +% +% :Examples: +% :: +% +% mvmt_file = 'path_to_BIDS_confounds_timeseries.tsv'; +% mvmt_data = importBIDSfile(mvmt_file); % fMRIprep-style confounds_timeseries.tsv +% mvmt_mtx = [mvmt_data.rot_x, mvmt_data.rot_y, mvmt_data.rot_z, ... +% mvmt_data.trans_x, mvmt_data.trans_y, mvmt_data.trans_z]; +% [fwd, mean_fwd, est_outliers, mvmt_mtx_mm] = framewise_displacement(mvmt_mtx); +% +% % Use a stricter outlier threshold of 0.25 mm +% [fwd, mean_fwd, est_outliers] = framewise_displacement(mvmt_mtx, 'thresh', 0.25); % -% Author: Michael Sun, Ph.D. 10/1/2024 +% :See also: +% - importBIDSfile % ------------------------------------------------------------------------- % DEFAULT ARGUMENT VALUES diff --git a/CanlabCore/OptimizeDesign11/GA2/construct_model_tmpwork.m b/CanlabCore/OptimizeDesign11/GA2/construct_model_tmpwork.m deleted file mode 100644 index b714e9fa..00000000 --- a/CanlabCore/OptimizeDesign11/GA2/construct_model_tmpwork.m +++ /dev/null @@ -1,352 +0,0 @@ -function [X,paramvec,conditions] = construct_model(mspec,conditions,paramvec,varargin) -%[X,paramvec] = construct_model(mspec,conditions,paramvec) -% -% paramvec is a matrix that completely specifies the choices of random variables -% to re-create a design, use the original conditions and paramvec -% -% if you specify variable trial onsets in condition(i).onsets, then any subsequent -% sub-trial parts you specify as fixed onset may be randomly chosen within the range -% of the first two sub-part onsets. Why? The algorithm creates a varying number of -% trials, so must have as many sub-trials of each type. -% -% dooverlap = 1 or 0, last (optional) argument; constrains events to not overlap with -% other trials -% -% Tor Wager - -paramindex = 1; -X = []; alltons = []; -if length(varargin) > 0, nooverlap = varargin{1};,else, nooverlap = 0;,end - - -if nooverlap - - alltons = get_all_trialonsets(conditions,mspec,paramvec,1); - -end - - -for i = 1:length(conditions) - - if conditions(i).parts == 1 - % make fixed regressor or set of regressors - if nooverlap - [x,paramvec,paramindex,ons] = cond2reg(conditions(i),mspec,paramvec,paramindex,alltons); - else - [x,paramvec,paramindex,ons] = cond2reg(conditions(i),mspec,paramvec,paramindex); - end - if isempty(X), X = x; else, X = [X x];,end - - else - - % get trial onsets - trialons = conditions(i).onsets; - if size(conditions(i).onsets,2) == 2 % variable range of trial onsets - if length(paramvec) < paramindex - % build new ones if they're not input - paramvec{paramindex} = onsetbuilder(conditions(i).onsets,mspec.numframes); - end - trialons = paramvec{paramindex}; - paramindex = paramindex + 1; - end - - % get subpart onsets - for j = 1:length(conditions(i).subcond) - if conditions(i).subcond(j).rel_to == 1 - conditions(i).subcond(j).baseons = trialons; - elseif conditions(i).subcond(j).rel_to == 2 - conditions(i).subcond(j).baseons = trialons; - conditions(i).subcond(j).add2base = ons; - else - error('rel_to field must be 1 or 2') - end - - % store onset parameters in paramvec{paramindex} - % return ons so that onsets for prev parts are stored in add2base and added to later event onset times - if nooverlap - [x,paramvec,paramindex,ons] = cond2reg(conditions(i).subcond(j),mspec,paramvec,paramindex,alltons); - else - [x,paramvec,paramindex,ons] = cond2reg(conditions(i).subcond(j),mspec,paramvec,paramindex); - end - if isempty(X), X = x; else, X = [X x];,end - end - - end - -end - -% add intercept -X(:,end+1) = 1; - -return - - - - - - - -% ----------------------------------------------------------------------------------- -% * sub-functions -% ----------------------------------------------------------------------------------- - -function [x,paramvec,paramindex,ons] = cond2reg(c,mspec,paramvec,paramindex,varargin) -% given a condition structure, returns regressor -% -% x is reg or set of regs to add to model -% paramvec is cell array of onsets (relative to...) for design reconstruction -% paramindex is index of which cell in paramvec to use for reconstruction of which condition -% ons is vector of trial onsets, for updating conditions, if necessary -% -% varargin is list of all trials, to constrain to no overlap - - if isfield(c,'parts') - if c.parts ~= 1 - % can't build regressor from trial type with parts - return - x = []; - return - end - end - - % determine onset times - % ----------------------------------------------------------------------------------- - delta = zeros(mspec.numframes,1); - if size(c.onsets,1) == 1 - % fixed onsets for condition, but sub-trial may be variable - - if isfield(c,'baseons') % for sub-parts of trials - - % if no range, just make it the same for all trials, no paramvec required - if ~any(diff(c.onsets)), c.onsets=repmat(c.onsets(1),1,size(c.baseons,2));,end - - if size(c.onsets,2) ~= size(c.baseons,2) %== 2 - % sub-trial, either range is given or length does not match variable trial onsets; get random - % uses range of c.onsets(1:2), so if spec as fixed, returns trials within this range - if length(paramvec) < paramindex, - paramvec{paramindex} = round(randrange(c.onsets(1:2),length(c.baseons))); - end - c.onsets = paramvec{paramindex}; - paramindex = paramindex + 1; - end - - len = min(length(c.baseons),length(c.onsets)); - - ons = c.onsets(1:len) + c.baseons(1:len); - - % replace baseons with onsets for previous trial for relative onsets - % leave baseons as the trial onsets in case we need to compute trial overlaps - if isfield(c,'add2base') - if ~isempty(c.add2base) - ons = c.onsets(1:len) + c.add2base(1:len); - end - end - - % constrain overlap, if specified - if length(varargin) > 0, - tons = varargin{1};, tons = [tons Inf]; - for i = 1:length(c.baseons) - wh = abs(c.baseons(i) - tons); - wh=find(wh == min(wh)); - wh = wh(1); - ons(i) = min(ons(i),tons(wh+1)-1); - end - end - - else - ons = c.onsets; - - end - - ons = round(ons); - - - ons(ons < 0) = 0; - - if any(1+ons == 0), warning('Some onsets are zero!'),ons,keyboard,end - if any(1+ons < 0), warning('Some onsets are less than zero!'),ons,keyboard,end - if ~isreal(1+ons), warning('Ons is not real!'),ons,keyboard,end - %if any(1+ons > length(delta)), warning('Onsets exceed length of delta!'),end - - try,delta(1 + ons) = 1;,catch, disp('Unknown error!'),keyboard,end - - % add repeated elements to delta, if specified - if c.stimlength > 1, - reps = repeatindex(ons,c.stimlength); - delta(1 + reps) = 1; - end - - else - % variable (random) onsets - if length(paramvec) < paramindex - % build new ones if they're not input - paramvec{paramindex} = onsetbuilder(c.onsets,mspec.numframes); - end - - if isfield(c,'baseons') % for sub-parts of trials - % I don't think this should ever happen. - ons = paramvec{paramindex} + c.baseons; - - % constrain overlap, if specified - if length(varargin) > 0, - tons = varargin{1};, tons = [tons Inf]; - for i = 1:length(c.baseons) - wh = abs(c.baseons(i) - tons); - wh=find(wh == min(wh)); - wh = wh(1); - ons(i) = min(ons(i),tons(wh+1)-1); - end - end - - else - ons = paramvec{paramindex}; - end - delta(1 + (paramvec{paramindex})) = 1; - paramindex = paramindex + 1; - - % if c is the main condition vector with only 1 part, then - % we don't have a stimlength field, so get it from the subpart - if ~isfield(c,'stimlength'), c.stimlength = c.subcond(1).stimlength;, end - - % add repeated elements to delta, if specified - if c.stimlength > 1, - reps = repeatindex(ons,c.stimlength); - delta(1 + reps) = 1; - end - end - - % convolve or shift - % ----------------------------------------------------------------------------------- - - if isfield(c,'parts') - if c.parts == 1 - doconv = c.subcond(1).convolve; - hrfest = c.subcond(1).hrfest; - else - error('This should never ever happen.') - end - else - % this c IS a subpart - doconv = c.convolve; - hrfest = c.hrfest; - end - - if doconv, - x = conv(mspec.hrf,delta); - elseif ~isempty(hrfest) - sf{1} = delta; - [x] = tor_make_deconv_mtx(sf,round(hrfest),1); - else - x = delta; - end - - x = x(1:ceil(mspec.numframes),:); - -return - - - - -% ----------------------------------------------------------------------------------- -% * fill the run with as many trials as possible, given range of random lengths and delays -% ----------------------------------------------------------------------------------- - -function [onsets] = onsetbuilder(range,numframes) -% builds list of trials with random onset times -% range is range of onset times in condition.onsets, a 2 x 2 matrix - -%if length(range) > size(range,1), range = range';, end - -onsets = []; tend = 0; % 0 is first time point in run -dlen = round(randrange(range(1,:),1)); -dnaindex = 2; - -while tend + dlen < numframes % while onset of next trial is < end of run - - tlen = round(randrange(range(2,:),1)); % trial length - if tend + dlen + tlen > numframes, break,end % trial will not fit in run - - % add a trial - onsets(end+1) = tend + dlen; % specify onset after start delay of dlen - tend = onsets(end) + tlen; % specify ending point of trial - dlen = round(randrange(range(1,:),1)); % re-randomize delay for next trial - -end - - - - - -% ----------------------------------------------------------------------------------- -% * get a random number or vector within a specified range -% ----------------------------------------------------------------------------------- -function rval = randrange(range,num) - rval = rand(1,num) * (range(1,2) - range(1,1)) + range(1,1); -return - - - -% ----------------------------------------------------------------------------------- -% * given onsets and length of stim, find indices of 'on' elements following onsets -% ----------------------------------------------------------------------------------- -function a = repeatindex(ons,stimlength); - stimlength = round(stimlength); - a = repmat(ons,stimlength-1,1); - b = (1:stimlength-1)'; b = repmat(b,1,size(ons,2)); - a = a + b; a = a(:); -return - - - - - - - -% ----------------------------------------------------------------------------------- -% * given conditions, get concatentated onsets of all trial types (conditions) -% ----------------------------------------------------------------------------------- - -function alltons = get_all_trialonsets(conditions,mspec,paramvec,paramindex,varargin) - -alltons = []; - -for i = 1:length(conditions) - - if conditions(i).parts == 1 - % make fixed regressor or set of regressors - [d1,d2,d3,ons] = cond2reg(conditions(i),mspec,paramvec,paramindex); - else - - % get trial onsets - ons = conditions(i).onsets; - if size(conditions(i).onsets,2) == 2 % variable range of trial onsets - if length(paramvec) < paramindex - % build new ones if they're not input - paramvec{paramindex} = onsetbuilder(conditions(i).onsets,mspec.numframes); - end - ons = paramvec{paramindex}; - - % add one for each subpart not constant - toadd = 1; - for j = 1:conditions(i).parts - if any(diff(conditions(i).subcond(j).onsets)) - toadd = toadd + 1; - end - end - paramindex = paramindex + toadd; % jump to next trial start - end - end % fixed or variable onsets - - alltons = [alltons ons]; - - -end % loop thru conditions - -alltons = sort(alltons); - - - - -return - - - diff --git a/CanlabCore/OptimizeDesign11/GA3/tmp_onsets2dx.m b/CanlabCore/OptimizeDesign11/GA3/tmp_onsets2dx.m deleted file mode 100644 index 98754e78..00000000 --- a/CanlabCore/OptimizeDesign11/GA3/tmp_onsets2dx.m +++ /dev/null @@ -1,18 +0,0 @@ - -% input in seconds - -TR = 2; % 1 = leave in seconds; 2, downsample by 2, etc. - -len = 200; % length in s -tp = 5; % estimates 20 time points - -scanspersess = [40 40 10]; % images per session; length is num sessions - -[X,delta,delta_hires,hrf] = onsets2delta(ons,TR,len); - -[DX,sf] = tor_make_deconv_mtx3(delta,tp,1,0,1,0,scanspersess); - - -% for phys data, .01 s -> 1s -Y = RESAMPLE(X,1,100); - diff --git a/CanlabCore/Statistics_tools/Iterative_Generalized_Least_Squares/igls_old.m b/CanlabCore/Statistics_tools/Iterative_Generalized_Least_Squares/igls_old.m deleted file mode 100644 index 0ae0cd02..00000000 --- a/CanlabCore/Statistics_tools/Iterative_Generalized_Least_Squares/igls_old.m +++ /dev/null @@ -1 +0,0 @@ -function out = igls(y, x, varargin) % function out = igls(y, x, [optional arguments]) % % Variance Component Estimation using IGLS/RIGLS % % y = d + cx + epsilon where z is an optional covariate and epsilon is an AR(p) process % % d ~ N(0, sigma_d) and c ~ N(0, sigma_c) % % Calculate sigma using the Yule-Walker method. Calculate d, c, b, sigma_d % and sigma_c using Maximum Likelihood % methods (IGLS) and Restricted Maximum Likelihood methods (RIGLS). % % This program is based on methods which are described in the following % papers: % % Goldstein, H. (1986). Multilevel mixed linear model analysis using % iterative generalized least squares. Biometrika 73, 43-56. % Goldstein, H. (1989). Restricted unbiased iterative generalized % least-squares estimation, Biometrika 76, 622-623. % % Inputs: % % y - matrix T x subjects % x - matrix T x subjects % % Optional inputs % % 'covariate' includes T x subjects 1st level covariate matrix or % subjects x 1 2nd level covariate vector. The dimensions % dictate the level. % 'noverbose' suppress verbose output % 'iter' max number of iterations % 'type' 'i' for igls (default) or 'r' for rigls % 'eps' epsilon for convergence : all changes < (epsilon * beta) % 'ar' order of AR(p) process; default is 0 (no AR model) % 'within_var' specify common variance within subjects % % Outputs: Saved in fields of out.(fieldname) % % beta: Group intercept and slope estimates % - vector of length 2. Contains estimates of d and c. % (d is intercept and c is slope) % betastar: Between-subjects (2nd level) error estimates % - vector of length 2. Contains estimates of sigma_d^2 (intercept variance) and % sigma_c^2 (slope variance). % Sigma: Within-subjects error estimates ****PROBABLY SIGMA^2..check**** % - vector of length sub. Contains an estimate of sigma for each % subject. % Cov_beta - matrix 2 x 2. Contains covariance matrix for beta. % - diagonals are variances, off-diagonals are covariances % % Cov_betastar - matrix 2 x 2. Contains covariance matrix for betastar. % iterations - number of iterations performed % elapsed_time - amount of time needed to run program % % By Martin Lindquist, April 2007 % Edits: Tor Wager, June 2007 % Martin Lindquist, July 2007 % Tor and Martin, July 2007 % Martin, January 2008 % Tor and Martin, March 2008 % Martin, July 2008 % Martin, October 2008 % % Example: Create simulated data and test % ---------------------------------------------------------------- % len = 200; sub = 20; % x = zeros(len,sub); % x(11:20,:) = 2; % create signal % x(111:120,:) = 2; % % c = normrnd(0.5,0.1,sub,1); % slope between-subjects variations % d = normrnd(3,0.2,sub,1); % intercept between-subjects variations % % % Create y: Add between-subjects error (random effects) and measurement noise % % (within-subjects error) % for i=1:sub, y(:,i) = d(i) + c(i).*x(:,i) + normrnd(0,0.5,len,1); % end; % % out = igls(y, x) % for igls % disp('Input random-effect variances: '); disp(std([d c])) % disp('Est. random-effect variances: '); disp(sqrt(out.betastar)'); % % Examples of more complete calls with optional arguments: % out = igls(y, x, 'type','r', 'iter', 10); beta,betastar % out = igls(y, x, 'ar', 2,'type','i', 'iter', 10, 'epsilon', .00001); beta, betastar % out = igls(y, x,'type','r', 'noverbose'); beta,betastar % % Small example, for matrix imaging % len = 20; sub = 5; randslopevar = 1; randintvar = 1; withinerr = 1; % fixedslope = 1; fixedint = 1; % x = zeros(len,sub); x(1:2:10, :) = 1; % fixed-effect signal (same for all subs) % c = normrnd(fixedslope,randslopevar,sub,1); % slope between-subjects variations % d = normrnd(fixedint,randintvar,sub,1); % intercept between-subjects variations % clear y % for i=1:sub, y(:,i) = d(i) + c(i).*x(:,i) + normrnd(0,withinerr,len,1); end % out = igls(y, x, 'plot', 'all') % for igls % % % Example: Simulation with second level covariate % % % len = 200; sub = 20; % x = zeros(len,sub); % x(11:20,:) = 2; % create signal % x(111:120,:) = 2; % % c = normrnd(0.5,0.1,sub,1); % slope between-subjects variations % d = normrnd(3,0.2,sub,1); % intercept between-subjects variations % % for i=1:sub, y(:,i) = d(i) + c(i).*x(:,i) + 2*i + normrnd(0,0.5,len,1); end; % % out = igls(y, x,'covariate',(1:20)); % for igls % disp('Input random-effect variances: '); disp(std([d c])) % disp('Est. random-effect variances: '); disp(sqrt(out.betastar)'); % % Programmers' notes % -------------------------------------------------------------------- % % Created 5/29/2007 by Martin Lindquist % Edits: 5/29, Tor; minor code rearrangement; results identical with % original version on test data % Replaced epsilon with data-dependent criterion % Speeded up code : almost 2 x as fast with \ operator and avoiding % growing arrays % 1.69 s on example code for 5 iterations % Added optional arguments: type and verbose % Edits: 7/1, Martin; Edited solution of betastar. Edited bug that % was underestimating the variance of betastar. Included option % to allow for common within subject variance acroos subjects. % Edits 7/13: Tor: plotting functions, optional inputs, more bookkeeping % stuff % Edits: 1/14/08: Martin: Implemented Likelihood Ratio Test for testing % the significance of the variance components. % Edits: 3/7/08: T & M: take cell inputs /optional % Edits: 7/14/08: Martin: Allowed for either 1st or 2nd level covariate. % % Simulation: No random effects % len = 200; sub = 20; % x = zeros(len,sub); % x(11:20,:) = 2; % create signal % x(111:120,:) = 2; % c = normrnd(0.5,.1,sub,1); % slope between-subjects variations % d = normrnd(3,.3,sub,1); % intercept between-subjects variations % y = x; % figure; imagesc(y) % y = x; % % Add between-subjects error (random effects) and measurement noise % % (within-subjects error) % for i=1:sub, y(:,i) = d(i) + c(i).*x(:,i) + normrnd(0,0.5,len,1); % end; % out = igls(y, x, 'type', 'r'); % for igls % disp('Input random-effect variances: '); disp(std([d c])) % disp('Est. random-effect variances: '); disp(sqrt(out.betastar)'); c1= clock; % outputs Phi = []; % defaults % ------------------------------------------------------------------- epsilon = 0.01; % Convergence criterion: Min change in beta * epsilon num_iter = 5; doverbose = 1; docovariate = 0; level = 1; % Determines which level to apply covariate arorder = 0; % or Zero for no AR type = 'i'; within = 'common'; % doplot = 'slopes'; doplot='none'; beta_names = {'Intcpt.' 'Slope1'}; % default names % optional inputs % ------------------------------------------------------------------- % for varg = 1:length(varargin) % if ischar(varargin{varg}) % switch varargin{varg} % % % reserved keywords % case 'verbose', doverbose = 1; % case 'noverbose', doverbose = 0; % % end % end % end for varg = 1:length(varargin) if ischar(varargin{varg}) switch varargin{varg} % reserved keywords case 'covariate', docovariate = 1; x_c = varargin{varg+1}; case 'verbose', doverbose = 1; case 'noverbose', doverbose = 0; case {'iterations', 'iter'}, num_iter = varargin{varg+1}; case 'type', type = varargin{varg+1}; varargin{varg+1} = []; case {'epsilon', 'eps'}, epsilon = varargin{varg+1}; case {'ar', 'arorder'} , arorder = varargin{varg+1}; case {'within_var', 'within'} , within = 'unique_est'; case {'noplot'}, doplot = 'off'; case {'plot'}, doplot = varargin{varg + 1}; varargin{varg + 1} = []; case 'names', beta_names = varargin{varg + 1}; otherwise, if doverbose, disp(['Unknown input string option: ' varargin{varg}]); end end end end % enforce matrix, padding if necessary; convert from cell input if % necessary [y, x] = cell2matrix(y, x); % check type switch type case 'i', analysisname = 'IGLS: Iterative generalized least squares analysis'; case 'r', analysisname = 'RIGLS: Restricted iterative generalized least squares analysis'; otherwise error('Type must be ''i'' (igls) or ''r'' (rigls)'); end % sizes, etc. % ------------------------------------------------------------------- % T, time points for subjects (same across subjects) % sub, number of subjects % T2, num. elements in lower triangle of cov matrix % len = total # obs % n_G # rows in G % z data, (Y1 Y2 ... Ysub)' % D within-subjects design, blk diagonal [T, sub] = size(y); % Length of y vector (Time) x Number of subjects T2 = T * (T + 1) ./ 2; % num. elements in lower triangle of cov matrix len = sub * T; % Total number of observations n_G = sub * T2; % number of rows in var-comp est. design matrix z = reshape(y,len,1); % Concatenated data if (docovariate == 1) if length(x_c) > size(x_c, 1), x_c = x_c'; end % is row; transpose x_c = x_c - nanmean(x_c); % Mean-center covariates; % If x_c is a vector than covariate is applied to 2nd level, if it is a % matrix than it is applied to the 1st level if (size(x_c, 1) == sub) % Set-up second level covariates x_c = repmat(x_c', T, 1); level = 2; else error(sprintf('Covariate must be %3.0f x 1 vector', sub)); end D = [zeros(len,1)+1 reshape(x,len,1) reshape(x_c,len,1)]; % Design matrix beta_names = {'Intcpt.' 'Slope1', 'Covariate'}; else D = [zeros(len,1)+1 reshape(x,len,1)]; % Design matrix end % Remove NaNs [whnan, D, z] = nanremove(D, z); %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % Step 1: Find the OLS solution % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% beta = D \ z; % Beta values resid = z - D * beta; % Residuals % Create regressors corresponding to within subject variance. % Dimensions depend on whether one assumes common variance across % subjects. if (strcmp(within, 'common')) V = zeros(n_G,1); ind_c = zeros(1,4)+1; ind_d = zeros(1,4)+1; else V = zeros(n_G,sub); ind_c = zeros(1,3+sub)+1; ind_d = zeros(1,3+sub)+1; end Sigma = zeros(sub,1); Sig = zeros(len,len); % Covariance matrix iSig = eye(len,len); % Inverse of covariance matrix Sig_no_d = zeros(len,len); % Covariance matrix for reduced model with sigma_d=0 (needed for LRT). iSig_no_d = eye(len,len); % Inverse of covariance matrix Sig_no_c = zeros(len,len); % Covariance matrix for reduced model with sigma_c=0 (needed for LRT). iSig_no_c = eye(len,len); % Inverse of covariance matrix if arorder > 0 Phi = zeros(sub,arorder); get_ar % updates Phi, Sigma, and -> V (from Sigma) else get_V_no_ar end ystar = zeros(n_G, 1); % Sums of squared residuals, concatenated across Ss ystar_no_c = zeros(n_G, 1); % SSR for reduced model 1 ystar_no_d = zeros(n_G, 1); % SSR for reduced model 2 resid_no_c = resid; % Set temporary values needed for first iteration. resid_no_d = resid; get_ystar; % Fit the variance parameter design matrix to ystar, est. residual variances G = Create_Design_Eq2(x,V); % Create design matrix for variance estimation betastar = G \ ystar; % Estimate variance components betastar(betastar < 0) = 0; % Use max(0,betastar) to ensure nonnegative variance. % Reduced model 1 ind_d(1) = 0; ind_d(3) = 0; tt_d = (ind_d == 1); betastar_no_d = G(:,tt_d) \ ystar; % Estimate variance components when sigma_d=0 betastar_no_d(betastar_no_d < 0) = 0; % Use max(0,betastar) to ensure nonnegative variance. % Reduced model 2 ind_c(2) = 0; ind_c(3) = 0; tt_c = (ind_c == 1); betastar_no_c = G(:,tt_c) \ ystar; % Estimate variance components when sigma_c=0 betastar_no_c(betastar_no_c < 0) = 0; % Use max(0,betastar) to ensure nonnegative variance. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % Step 2: Iterate % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% iterations = 0; min_change = betastar * epsilon; isconverged = 0; while (iterations < num_iter) && ~isconverged num = size(G,1) / sub; for s = 1:sub Sig1 = ivech(G(((s-1) * num + 1):(s * num),:) * betastar); Sig1 = Sig1 + tril(Sig1,-1)'; wh = ((s-1)*T+1):(s*T); % which indices in Cov mtx for this subject Sig(wh, wh) = Sig1; iSig(wh, wh) = inv(Sig(wh, wh)); % Cov mtx when sigma_d =0 Sig1_no_d = ivech(G(((s-1) * num + 1):(s * num),tt_d) * betastar_no_d); Sig1_no_d = Sig1_no_d + tril(Sig1_no_d,-1)'; Sig_no_d(wh, wh) = Sig1_no_d; iSig_no_d(wh, wh) = inv(Sig_no_d(wh, wh)); % Cov mtx when sigma_c =0 Sig1_no_c = ivech(G(((s-1) * num + 1):(s * num),tt_c) * betastar_no_c); Sig1_no_c = Sig1_no_c + tril(Sig1_no_c,-1)'; Sig_no_c(wh, wh) = Sig1_no_c; iSig_no_c(wh, wh) = inv(Sig_no_c(wh, wh)); end beta = inv(D'*iSig*D)*D'*iSig*z; % Beta values resid = z - D*beta; % Residuals beta_no_d = inv(D'*iSig_no_d*D)*D'*iSig_no_d*z; % Beta values when sigma_d=0 resid_no_d = z - D*beta_no_d; % Residuals beta_no_c = inv(D'*iSig_no_c*D)*D'*iSig_no_c*z; % Beta values when sigma_c=0 resid_no_c = z - D*beta_no_c; % Residuals betastar_old = betastar; get_ystar; beta_indiv = get_indiv_betas; % params x subjects matrix of betas betastar = G \ ystar; betastar(betastar < 0) = 0; % Use max(0,betastar) to ensure nonnegative variance. betastar_no_d = G(:,tt_d) \ ystar_no_d; betastar_no_d(betastar_no_d < 0) = 0; % Use max(0,betastar) to ensure nonnegative variance. betastar_no_c = G(:,tt_c) \ ystar_no_c; betastar_no_c(betastar_no_c < 0) = 0; % Use max(0,betastar) to ensure nonnegative variance. isconverged = ~any(abs(betastar - betastar_old) > abs(min_change)); iterations = iterations + 1; end Cov_beta = inv(D'*iSig*D); %W = (Sigma - D*inv(D'*iSigma*D)*D'); % Residual inducing matrix x Sigma df_beta = sub - 1; % this should be sub - q, # params in Xg, but we haven't added this flexibility yet %df_beta = (trace(W).^2)./trace(W*W); % Satterthwaite approximation for degrees of freedom Cov_betastar = inv(G'*G); df_betastar = sub - 1; Sigma = betastar(4:end); yhat = G*betastar; e = ystar - yhat; tau = e'*e/(size(G,1)-size(G,2)); %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Likelihood ratio tests %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% detmat = zeros(sub,1); detmat_no_d = zeros(sub,1); detmat_no_c = zeros(sub,1); detDSigD = 0; detDSigD_no_d = 0; detDSigD_no_c = 0; for k=1:sub, wh = ((k-1) * T + 1):(k * T); detmat(k) = abs(det(Sig(wh,wh))); detmat_no_d(k) = abs(det(Sig_no_d(wh,wh))); detmat_no_c(k) = abs(det(Sig_no_c(wh,wh))); detDSigD = detDSigD+abs(det(D(wh,:)' * iSig(wh,wh) * D(wh,:))); detDSigD_no_d = detDSigD_no_d+abs(det(D(wh,:)' * iSig_no_d(wh,wh) * D(wh,:))); detDSigD_no_c = detDSigD_no_c+abs(det(D(wh,:)' * iSig_no_c(wh,wh) * D(wh,:))); end; % detDSigD = abs(det(D' * iSig * D)); % detDSigD_no_d = abs(det(D' * iSig_no_d * D)); % detDSigD_no_c = abs(det(D' * iSig_no_c * D)); % % Test H0: sigma_d =0 LLd = - 0.5*( sum(log(detmat_no_d))-sum(log(detmat)) + (resid_no_d'* iSig_no_d * resid_no_d - resid'* iSig * resid)); if (type == 'r'), LLd = -0.5*(log(detDSigD_no_d) - log(detDSigD) + sum(log(detmat_no_d))-sum(log(detmat)) +... (resid'* iSig_no_d * resid - resid'* iSig * resid)); end; LRd = max(-2*LLd,0); % randvariance_d = 1-chi2cdf(LRd,1); %Make it a 50:50 mixture V = 0.5*(chi2rnd(1,1000000,1)) + 0.5*(chi2rnd(2,1000000,1)); randvariance_d = mean(V>LRd); % Test H0: sigma_c =0 LLc = - 0.5*( sum(log(detmat_no_c))-sum(log(detmat)) + (resid_no_c'* iSig_no_c * resid_no_c - resid'* iSig * resid)); if (type == 'r'), LLc = -0.5*(log(detDSigD_no_c) - log(detDSigD) + sum(log(detmat_no_c))-sum(log(detmat)) +... (resid'* iSig_no_c * resid - resid'* iSig * resid)); end; LRc = max(-2*LLc,0); % randvariance_c = 1-chi2cdf(2*LRc,1); randvariance_c = mean(V>LRc); betastar = betastar(1:2); % Remove within subject variance Cov_betastar = Cov_betastar(1:2,1:2); % Remove within subject variance c2 = clock; elapsed_time = etime(c2, c1); % save output structure names = {'Y1'}; % later; varnames = {'analysisname', 'names', 'beta_names', 'type', 'num_iter', 'epsilon', 'arorder', 'within', 'y', 'x'}; inputOptions = create_struct(varnames); out = struct('analysisname', analysisname, 'beta', beta, 'betastar', betastar, 'beta_indiv', beta_indiv, 'beta_names', {beta_names}, ... 'Cov_beta', Cov_beta, 'Cov_betastar', Cov_betastar, ... 'Sigma', Sigma, 'Phi', Phi, ... 'type', type, 'arorder', arorder, 'isconverged', isconverged, ... 'num_obs', T, 'sub', sub, 'num_iter', num_iter, 'epsilon', epsilon, ... 'iterations', iterations, 'elapsed_time', elapsed_time, 'inputOptions', inputOptions); % save stats out.ste = sqrt(diag(out.Cov_beta)); out.t = out.beta ./ out.ste; out.df_beta = df_beta; out.p = 2 * (1 - tcdf(abs(out.t), out.df_beta)); % two-tailed out.p(out.p == 0) = eps; out.p_tails = 'two-tailed'; out.t_randvariance = out.betastar ./ sqrt(diag(out.Cov_betastar)); out.df_betastar = df_betastar; out.p_randvariance = (1 - tcdf(abs(out.t_randvariance), out.df_betastar)); % one-tailed out.p_randvariance(out.p_randvariance == 0) = eps; out.p_randvariance_tails = 'one-tailed'; out.LRT = [LRd; LRc]; out.pLRT_randvariance = [randvariance_d; randvariance_c]; if strcmp(doplot, 'all') plot_igls_matrices igls_plot_slopes(out, x); elseif strcmp(doplot, 'slopes') igls_plot_slopes(out, x); end % print output if doverbose print_output_text(out) end % _________________________________________________________________________ % % % % * Inline (nested) functions % % % %__________________________________________________________________________ function newstruct = create_struct(varnames) newstruct = struct(); for i = 1:length(varnames) eval(['newstruct.(varnames{i}) = ' varnames{i} ';']); end end function [beta_indiv] = get_indiv_betas % beta_indiv is params x subjects matrix % Reexpress design matrix depending on whether or not the covariate % is applied to the 1st or second level. DD = D; if (level == 2) DD = DD(:,1:(end-1)); %Remove second level covariate from design matrix end beta_indiv = zeros(size(DD,2),sub); for k = 1:sub % Estimate AR parameters using the Yule-Walker method for each subject. wh = ((k-1) * T + 1):(k * T); beta_indiv(:,k) = inv(DD(wh,:)' * iSig(wh,wh) * DD(wh,:)) * DD(wh,:)' * iSig(wh,wh) * z(wh, 1); sigma_indiv(k) = mean(diag(Sig(wh, wh))); %% for plotting: need weight estimates for each subject end end function get_V_no_ar mysig = vech(eye(T)); for k = 1:sub wh = ((k-1) * T2 + 1):(k * T2); % indices in time series for this subject if (strcmp(within, 'common')) V(wh) = mysig; % Create one regressor for common variance else V(wh,k) = mysig; % Cretae m regressors otherwise end end end function get_ar % % beta_indiv = zeros(size(D,2),sub); for k = 1:sub % Estimate AR parameters using the Yule-Walker method for each subject. wh = ((k-1) * T + 1):(k * T); % % beta_indiv(:,k) = (D(wh,:) \ z(wh)); % % res = z(wh) - (D(wh,:) * beta_indiv(:,k)); [a,e] = aryule(resid(wh), arorder); % Yule-Walker Phi(k,:) = a(2:(arorder+1)); % Parameters of AR(p) model Sigma(k) = sqrt(e); % standard deviation of AR(p) model; ***not used?*** % Find the covariance matrix A = diag(ones(T,1)); for j=1:arorder A = A + diag(Phi(k,j)*ones((T-j),1),-j); end wh = ((k-1) * T2 + 1):(k * T2); % indices in time series for this subject iA = inv(A); tmp = vech(iA * iA'); if strcmp(within, 'common') % V(wh) = Sigma(k)^2 * tmp; % Covariance function in vech format V(wh) = tmp; % Covariance function in vech format else % V(wh,k) = Sigma(k)^2 * tmp; % Covariance function in vech format V(wh,k) = tmp; % Covariance function in vech format end end end function get_ystar if (type == 'i') % IGLS for i=1:sub wh = ((i-1) * T + 1):(i * T); % indices in time series for this subject myresid = resid( wh ); % residuals for this subject. myresid_no_c = resid_no_c(wh ); myresid_no_d = resid_no_d(wh ); tmp = vech(myresid * myresid'); % Find vech of estimated covariance tmp_no_c = vech(myresid_no_c * myresid_no_c'); tmp_no_d = vech(myresid_no_d * myresid_no_d'); wh = ((i-1) * T2 + 1):(i * T2); % indices in time series for this subject ystar(wh) = tmp; ystar_no_c(wh) = tmp_no_c; ystar_no_d(wh) = tmp_no_d; end elseif (type == 'r') % RIGLS DD = D; if (level == 2) DD = DD(:,1:(end-1)); %Remove second level covariate from design matrix end for i=1:sub wh = ((i-1) * T + 1):(i*T); % indices in time series for this subject Dtmp = DD(wh, :); rtmp = resid(wh); % residuals for this subject. rtmp_no_c = resid_no_c(wh); rtmp_no_d = resid_no_d(wh); wh = ((i-1) * T2 + 1):(i * T2); Vtmp = V(wh); U = ivech(Vtmp); iU = inv(U); iU_no_c = iU; iU_no_d = iU; % iU = iSig(wh,wh); % iU_no_c = iSig_no_c(wh,wh); % iU_no_d = iSig_no_d(wh,wh); rig = rtmp * rtmp' + Dtmp * inv(Dtmp' * iU * Dtmp) * Dtmp'; tmp = vech(rig); % Find vech of estimated covariance rig_no_c = rtmp_no_c * rtmp_no_c' + Dtmp * inv(Dtmp' * iU_no_c * Dtmp) * Dtmp'; tmp_no_c = vech(rig_no_c); % Find vech of estimated covariance rig_no_d = rtmp_no_d * rtmp_no_d' + Dtmp * inv(Dtmp' * iU_no_d * Dtmp) * Dtmp'; tmp_no_d = vech(rig_no_d); % Find vech of estimated covariance wh = ((i-1) * T2 + 1):(i * T2); % indices in time series for this subject ystar(wh) = tmp; ystar_no_c(wh) = tmp_no_c; ystar_no_d(wh) = tmp_no_d; end end end function plot_igls_matrices % Ystar_mtx: Estimate of total covariance, var(ksi) Ystar_mtx = single(resid * resid'); figh = create_figure(['IGLS Error Matrix Plot'], 2, 4); for i = 1:7, subplot(2, 4, i); set(gca,'YDir', 'reverse'); end subplot(2, 4, 8); axis off titles = {'Cov(ksi) est.' 'Random intercept' 'Random slope' 'Within error' 'Fitted' 'residual'}; subplot(2, 4, 1); s = std(Ystar_mtx(:)); clims = [-3 * s 3 * s]; imagesc(Ystar_mtx, clims) title(titles{1}) xlabel('T * N'); ylabel('T * N'); axis image text(T*sub + .01*T*sub, T*sub/2, '=','FontSize', 64) % set color map; make zero values white cm = zeros(256, 3); cvec = abs(linspace(clims(1), clims(2), size(cm, 1))); wh = find(cvec == min(cvec)); wh = wh(1); hotpart = [linspace(.9, 1, length(cvec) - wh + 1)' linspace(1, 0, length(cvec) - wh + 1)' linspace(0, 0, length(cvec) - wh + 1)']; cm(wh:end, :) = hotpart(end:-1:1, :); coolpart = [linspace(0, 0, wh - 1)' linspace(0, 1, wh - 1)' linspace(1, .9, wh - 1)']; cm(1:wh - 1, :) = coolpart; cm(wh-1:wh+1, :) = repmat([.3 .3 .3], 3, 1); colormap(cm) drawnow num_var_comps = size(G, 2) - length(Sigma); % GGmtx: Cell array of matrix error covariance components % ----------------------------------------- myest = [betastar; Sigma]; GGfit = sparse(zeros(T*sub, T*sub)); % get var. component matrices, not including Sigma (within error) for k = 1:num_var_comps for i = 1:sub wh = ((i-1) * T2 + 1):(i * T2); % indices in time series for this subject GG{i} = ivech(G(wh, k)); end % fitted for this component GGmtx{k} = sparse(blkdiag(GG{:})) .* myest(k); GGmtx{k} = full_from_ltr( GGmtx{k} ); % full matrix form from lower triangle GGfit = GGfit + GGmtx{k}; end % now get the one for sigma (V) % ----------------------------------------- wh_is_V = num_var_comps + 1; GGmtx{wh_is_V} = sparse(zeros(T * sub, T * sub)); for k = 1:length(Sigma) for i = 1:sub wh = ((i-1) * T2 + 1):(i * T2); % indices in time series for this subject GG{i} = ivech(G(wh, num_var_comps + k)); end % fitted error GGmtx{wh_is_V} = GGmtx{wh_is_V} + sparse(blkdiag(GG{:})) .* myest(num_var_comps + k); end GGmtx{wh_is_V} = full_from_ltr( GGmtx{wh_is_V} ); % full matrix form from lower triangle GGfit = GGfit + GGmtx{wh_is_V}; % image them % ----------------------------------------- for k = 1:num_var_comps + 1 subplot(2, 4, k + 1); imagesc(GGmtx{k}, clims); title(sprintf('%s\nb-hat = %3.3f', titles{k+1}, myest(k))); axis image if k <= num_var_comps, text(T*sub + .01*T*sub, T*sub/2, '+','FontSize', 64), end drawnow end subplot(2, 4, num_var_comps + 3); imagesc(GGfit, clims) title(titles{5}) axis image drawnow residmtx = double(Ystar_mtx) - GGfit; clear GGfit fprintf('MST (diag of Ystar_mtx): %3.5f\n', mean(diag(Ystar_mtx))); fprintf('MSE (diag of resid): %3.5f\n', mean(diag(residmtx))); axh = subplot(2, 4, num_var_comps + 4); imagesc(residmtx, clims) title(titles{6}) axis image drawnow axh2 = subplot(2, 4, num_var_comps + 5); axes(axh2) imagesc(residmtx, clims) colorbar; %('peer', axh); set(axh2,'Visible','off') end end % END MAIN FUNCTION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % Subfunctions % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% function G = Create_Design_Eq2(x,V) % function G = Create_Design(x) % % Create Design matrix for estimation of variance components % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% [T, sub] = size(x); T2 = T * (T+1) / 2; one = zeros(T,1) + 1; G = zeros(sub * T2, 3); for i = 1:sub wh = ((i-1) * T2 + 1):(i * T2); G(wh,1) = vech(one*one'); % Regressor corresponding to sigma_d^2 G(wh,2) = vech(x(:,i) * x(:,i)'); % Regressor corresponding to sigma_c^2 G(wh,3) = vech(one * x(:,i)' + x(:,i) * one'); % Regressor corresponding to sigma_dc end G = [G V]; % use within-subject covariance as a regressor end %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% function V = vech(Mat) % function V = vech(Mat) % % Calculate vech for the matrix Mat % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% V = Mat(tril(true(size(Mat)))); end %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% function Mat = ivech(V) % function Mat = vech(V) % % Calculate the "inverse" of the vech function % This is much faster than matlab's squareform.m % It could be speeded up, probably, by operating column-wise % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% len = length(V); dim = -0.5 + sqrt(0.25 + 2 * len); Mat = zeros(dim, dim); ind=1; for i=1:dim for j=i:dim Mat(j,i) = V(ind); ind = ind+1; end end end %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% function print_output_text(out) fprintf('\nigls.m output report:\n---------------------------------------\n'); fprintf('Data: %3.0f observations x %3.0f subjects \n', out.num_obs, out.sub); typestr = {'igls' 'rigls'}; nystr = {'No' 'Yes'}; fprintf('Fit Type: %s\n', typestr{strcmp(out.type, 'r') + 1}); fprintf('AR(p) model: %s', nystr{(out.arorder > 0) + 1}); if out.arorder > 0 fprintf(', AR(%d)\n', out.arorder); else fprintf('\n'); end fprintf('Converged: %s\n', nystr{out.isconverged + 1}); fprintf('Max iterations: %3.0f, Completed iterations: %3.0f\n', out.num_iter, out.iterations); fprintf('Epsilon for convergence: %3.6f\n', out.epsilon); fprintf('Elapsed time: %3.2f s\n', out.elapsed_time); fprintf('\nStatistics: Tests of inference on fixed population parameters\n') fprintf('Parameter\test.\tt(%3.0f)\tp\t\n', out.df_beta) sigstring = {' ' '+' '*' '**' '***'}; for i = 1:length(out.beta) % if i == 1, name = 'Intcpt.'; else name = ['Pred' num2str(i - 1)]; end % names input above, at start sig = out.p(i) < [Inf .1 .05 .01 .001]; fprintf('%s\t%3.3f\t%3.2f\t%3.6f\t%s\n', out.beta_names{i}, out.beta(i), out.t(i), out.p(i), sigstring{find(sig, 1, 'last')}) end fprintf('\n\nStatistics: Tests of significance on random effects (variances)\n') % fprintf('Parameter\test.\tt(%3.0f)\tp\t\n', out.df_betastar) fprintf('Parameter\test.\tLRT\tp\t\n') sigstring = {' ' '+' '*' '**' '***'}; for i = 1:length(out.beta) %if i == 1, name = 'Intcpt.'; else name = ['Pred' num2str(i - 1)]; end if (~strcmp(out.beta_names(i),'Covariate')) % sig = out.p_randvariance(i) < [Inf .1 .05 .01 .001]; % fprintf('%s\t%3.3f\t%3.2f\t%3.6f\t%s\n', out.beta_names{i}, out.betastar(i), out.t_randvariance(i), out.p_randvariance(i), sigstring{find(sig, 1, 'last')}) sig = out.pLRT_randvariance(i) < [Inf .1 .05 .01 .001]; fprintf('%s\t%3.3f\t%3.2f\t%3.6f\t%s\n', out.beta_names{i}, out.betastar(i), out.LRT(i), out.pLRT_randvariance(i), sigstring{find(sig, 1, 'last')}) end end fprintf('\n---------------------------------------\n\n'); end %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% function M = full_from_ltr(M) tmp2 = M; tmp2 = ( tmp2 .* (1 - eye(size(tmp2))) )'; M = M + tmp2; end % This duplicates a function in the SCN Core toolbox, but is included to % make igls.m stand-alone function f1 = create_figure(tagname, varargin) % f1 = create_figure(['tagname'], [subplotrows], [subplotcols], [do not clear flag]) % % checks for old figure with tag of tagname % clears it if it exists, or creates new one if it doesn't if nargin < 1 || isempty(tagname) tagname = 'nmdsfig'; end doclear = 1; % clear if new or if old and existing if length(varargin) > 2 && varargin{3} % use same figure; do not clear doclear = 0; end old = findobj('Tag', tagname); if ~isempty(old) if doclear, clf(old); end f1 = old; else % Or create new scnsize = get(0,'ScreenSize'); xdim = min(scnsize(3)./2, 700); ydim = min(scnsize(4)./2, 700); f1 = figure('position',round([50 50 xdim ydim]),'color','white'); set(f1, 'Tag', tagname, 'Name', tagname); end % activate this figure figure(f1); if doclear % true for new figs or cleared ones % Create subplots, if requested; set axis font sizes if length(varargin) > 0 i = max(1, varargin{1}); j = max(1, varargin{2}); else i = 1; j = 1; end np = max(1, i * j); for k = 1:np axh(k) = subplot(i,j,k); set(gca,'FontSize',18),hold on end axes(axh(1)); end end function [y, x] = cell2matrix(y, x) % one or none of y and x can be cell arrays if ~iscell(x) && ~iscell(y) return end if iscell(y) if ~iscell(x), error('y and x must either both be cell arrays or neither can be.'); end sz = zeros(size(y, 1), 2); for i = 1:length(y) sz(i, :) = size(y{i}); end sz = sz(:, 1); if all(sz) == sz(1) % all the same? else mx = max(sz(:, 1)); % longest for i = 1:length(y) if sz(i) < mx mn = mean(y{i}); mnx = mean(x{i}); y{i} = [y{i}; mn(ones(mx - sz(i), 1))]; % assume y and x match and are both cells x{i} = [x{i}; mnx(ones(mx - sz(i), 1))]; end end end %turn into matrix y = cell2mat(y); x = cell2mat(x); end end \ No newline at end of file diff --git a/CanlabCore/Statistics_tools/Iterative_Generalized_Least_Squares/igls_old2.m b/CanlabCore/Statistics_tools/Iterative_Generalized_Least_Squares/igls_old2.m deleted file mode 100644 index 360168b9..00000000 --- a/CanlabCore/Statistics_tools/Iterative_Generalized_Least_Squares/igls_old2.m +++ /dev/null @@ -1 +0,0 @@ -function out = igls(y, x, varargin) % function out = igls(y, x, [optional arguments]) % % Variance Component Estimation using IGLS/RIGLS % % y = d + cx + epsilon where z is an optional covariate and epsilon is an AR(p) process % % d ~ N(0, sigma_d) and c ~ N(0, sigma_c) % % Calculate sigma using the Yule-Walker method. Calculate d, c, b, sigma_d % and sigma_c using Maximum Likelihood % methods (IGLS) and Restricted Maximum Likelihood methods (RIGLS). % % This program is based on methods which are described in the following % papers: % % Goldstein, H. (1986). Multilevel mixed linear model analysis using % iterative generalized least squares. Biometrika 73, 43-56. % Goldstein, H. (1989). Restricted unbiased iterative generalized % least-squares estimation, Biometrika 76, 622-623. % % Inputs: % % y - matrix T x subjects % x - matrix T x subjects % % Optional inputs % % 'covariate' includes T x subjects 1st level covariate matrix or % subjects x 1 2nd level covariate vector. The dimensions % dictate the level. % 'noverbose' suppress verbose output % 'iter' max number of iterations % 'type' 'i' for igls (default) or 'r' for rigls % 'eps' epsilon for convergence : all changes < (epsilon * beta) % 'ar' order of AR(p) process; default is 0 (no AR model) % 'within_var' specify common variance within subjects % % Outputs: Saved in fields of out.(fieldname) % % beta: Group intercept and slope estimates % - vector of length 2. Contains estimates of d and c. % (d is intercept and c is slope) % betastar: Between-subjects (2nd level) error estimates % - vector of length 2. Contains estimates of sigma_d^2 (intercept variance) and % sigma_c^2 (slope variance). % Sigma: Within-subjects error estimates ****PROBABLY SIGMA^2..check**** % - vector of length sub. Contains an estimate of sigma for each % subject. % Cov_beta - matrix 2 x 2. Contains covariance matrix for beta. % - diagonals are variances, off-diagonals are covariances % % Cov_betastar - matrix 2 x 2. Contains covariance matrix for betastar. % iterations - number of iterations performed % elapsed_time - amount of time needed to run program % % By Martin Lindquist, April 2007 % Edits: Tor Wager, June 2007 % Martin Lindquist, July 2007 % Tor and Martin, July 2007 % Martin, January 2008 % Tor and Martin, March 2008 % Martin, July 2008 % Martin, October 2008 % % Example: Create simulated data and test % ---------------------------------------------------------------- % len = 200; sub = 20; % x = zeros(len,sub); % x(11:20,:) = 2; % create signal % x(111:120,:) = 2; % % c = normrnd(0.5,0.1,sub,1); % slope between-subjects variations % d = normrnd(3,0.2,sub,1); % intercept between-subjects variations % % % Create y: Add between-subjects error (random effects) and measurement noise % % (within-subjects error) % for i=1:sub, y(:,i) = d(i) + c(i).*x(:,i) + normrnd(0,0.5,len,1); % end; % % out = igls(y, x) % for igls % disp('Input random-effect variances: '); disp(std([d c])) % disp('Est. random-effect variances: '); disp(sqrt(out.betastar)'); % % Examples of more complete calls with optional arguments: % out = igls(y, x, 'type','r', 'iter', 10); beta,betastar % out = igls(y, x, 'ar', 2,'type','i', 'iter', 10, 'epsilon', .00001); beta, betastar % out = igls(y, x,'type','r', 'noverbose'); beta,betastar % % Small example, for matrix imaging % len = 20; sub = 5; randslopevar = 1; randintvar = 1; withinerr = 1; % fixedslope = 1; fixedint = 1; % x = zeros(len,sub); x(1:2:10, :) = 1; % fixed-effect signal (same for all subs) % c = normrnd(fixedslope,randslopevar,sub,1); % slope between-subjects variations % d = normrnd(fixedint,randintvar,sub,1); % intercept between-subjects variations % clear y % for i=1:sub, y(:,i) = d(i) + c(i).*x(:,i) + normrnd(0,withinerr,len,1); end % out = igls(y, x, 'plot', 'all') % for igls % % % Example: Simulation with second level covariate % % % len = 200; sub = 20; % x = zeros(len,sub); % x(11:20,:) = 2; % create signal % x(111:120,:) = 2; % % c = normrnd(0.5,0.1,sub,1); % slope between-subjects variations % d = normrnd(3,0.2,sub,1); % intercept between-subjects variations % % for i=1:sub, y(:,i) = d(i) + c(i).*x(:,i) + 2*i + normrnd(0,0.5,len,1); end; % % out = igls(y, x,'covariate',(1:20)); % for igls % disp('Input random-effect variances: '); disp(std([d c])) % disp('Est. random-effect variances: '); disp(sqrt(out.betastar)'); % % Programmers' notes % -------------------------------------------------------------------- % % Created 5/29/2007 by Martin Lindquist % Edits: 5/29, Tor; minor code rearrangement; results identical with % original version on test data % Replaced epsilon with data-dependent criterion % Speeded up code : almost 2 x as fast with \ operator and avoiding % growing arrays % 1.69 s on example code for 5 iterations % Added optional arguments: type and verbose % Edits: 7/1, Martin; Edited solution of betastar. Edited bug that % was underestimating the variance of betastar. Included option % to allow for common within subject variance acroos subjects. % Edits 7/13: Tor: plotting functions, optional inputs, more bookkeeping % stuff % Edits: 1/14/08: Martin: Implemented Likelihood Ratio Test for testing % the significance of the variance components. % Edits: 3/7/08: T & M: take cell inputs /optional % Edits: 7/14/08: Martin: Allowed for either 1st or 2nd level covariate. % % Simulation: No random effects % len = 200; sub = 20; % x = zeros(len,sub); % x(11:20,:) = 2; % create signal % x(111:120,:) = 2; % c = normrnd(0.5,.1,sub,1); % slope between-subjects variations % d = normrnd(3,.3,sub,1); % intercept between-subjects variations % y = x; % figure; imagesc(y) % y = x; % % Add between-subjects error (random effects) and measurement noise % % (within-subjects error) % for i=1:sub, y(:,i) = d(i) + c(i).*x(:,i) + normrnd(0,0.5,len,1); % end; % out = igls(y, x, 'type', 'r'); % for igls % disp('Input random-effect variances: '); disp(std([d c])) % disp('Est. random-effect variances: '); disp(sqrt(out.betastar)'); c1= clock; % outputs Phi = []; % defaults % ------------------------------------------------------------------- epsilon = 0.01; % Convergence criterion: Min change in beta * epsilon num_iter = 5; doverbose = 1; docovariate = 0; level = 1; % Determines which level to apply covariate arorder = 0; % or Zero for no AR type = 'i'; within = 'common'; % doplot = 'slopes'; doplot='none'; beta_names = {'Intcpt.' 'Slope1'}; % default names % optional inputs % ------------------------------------------------------------------- % for varg = 1:length(varargin) % if ischar(varargin{varg}) % switch varargin{varg} % % % reserved keywords % case 'verbose', doverbose = 1; % case 'noverbose', doverbose = 0; % % end % end % end for varg = 1:length(varargin) if ischar(varargin{varg}) switch varargin{varg} % reserved keywords case 'covariate', docovariate = 1; x_c = varargin{varg+1}; case 'verbose', doverbose = 1; case 'noverbose', doverbose = 0; case {'iterations', 'iter'}, num_iter = varargin{varg+1}; case 'type', type = varargin{varg+1}; varargin{varg+1} = []; case {'epsilon', 'eps'}, epsilon = varargin{varg+1}; case {'ar', 'arorder'} , arorder = varargin{varg+1}; case {'within_var', 'within'} , within = 'unique_est'; case {'noplot'}, doplot = 'off'; case {'plot'}, doplot = varargin{varg + 1}; varargin{varg + 1} = []; case 'names', beta_names = varargin{varg + 1}; otherwise, if doverbose, disp(['Unknown input string option: ' varargin{varg}]); end end end end % enforce matrix, padding if necessary; convert from cell input if % necessary [y, x] = cell2matrix(y, x); % check type switch type case 'i', analysisname = 'IGLS: Iterative generalized least squares analysis'; case 'r', analysisname = 'RIGLS: Restricted iterative generalized least squares analysis'; otherwise error('Type must be ''i'' (igls) or ''r'' (rigls)'); end % sizes, etc. % ------------------------------------------------------------------- % T, time points for subjects (same across subjects) % sub, number of subjects % T2, num. elements in lower triangle of cov matrix % len = total # obs % n_G # rows in G % z data, (Y1 Y2 ... Ysub)' % D within-subjects design, blk diagonal [T, sub] = size(y); % Length of y vector (Time) x Number of subjects T2 = T * (T + 1) ./ 2; % num. elements in lower triangle of cov matrix len = sub * T; % Total number of observations n_G = sub * T2; % number of rows in var-comp est. design matrix z = reshape(y,len,1); % Concatenated data, T within sub if (docovariate == 1) % If x_c is a vector than covariate is applied to 2nd level, if it is a % matrix than it is applied to the 1st level switch numel(x_c) case sub % Set-up second level covariates % enforce column format input if length(x_c) > size(x_c, 1), x_c = x_c'; end x_c = scale(x_c, 1); % Mean-center covariates across sub x_c = repmat(x_c',T,1); level = 2; case len % We have first-level covariate, T x sub x_c = scale(x_c, 1); % Mean-center covariates across time level = 1; otherwise error(sprintf('Covariate must be %3.0f x 1 vector for 2nd level or %3.0f x %3.0f matrix for 1st level', sub, sub, T)); end xvec = reshape(x,len,1); x_cvec = reshape(x_c,len,1); % D = [zeros(len,1)+1 xvec x_cvec xvec.*x_cvec]; % Design matrix beta_names = {'Intcpt. (1st level)' 'Slope1 (1st level)', 'Covariate_x_intcpt (2nd level)', 'Covariate_x_slope (2nd level)'}; D = [ones(len,1) xvec]; % design matrix, for later D1 = [x_cvec xvec.*x_cvec]; % covariate design matrix; cov on intercept, then cov * slope (cov * x) beta_c = D1\(z - nanmean(z)); z = z - D1 * beta_c; % use residuals for within-subjects effects below sig_c = z'*z / (len-2); % sigma^2, SS / df Cov_beta_c = sig_c .* inv(D1'*D1); % sigma^2 * inv(X'X) else D = [zeros(len,1)+1 reshape(x,len,1)]; % Design matrix end % Remove NaNs [whnan, D, z] = nanremove(D, z); %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % Step 1: Find the OLS solution % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% beta = D \ z; % Beta values resid = z - D * beta; % Residuals % Create regressors corresponding to within subject variance. % Dimensions depend on whether one assumes common variance across % subjects. if (strcmp(within, 'common')) V = zeros(n_G,1); ind_c = zeros(1,4)+1; ind_d = zeros(1,4)+1; else V = zeros(n_G,sub); ind_c = zeros(1,3+sub)+1; ind_d = zeros(1,3+sub)+1; end Sigma = zeros(sub,1); Sig = zeros(len,len); % Covariance matrix iSig = eye(len,len); % Inverse of covariance matrix Sig_no_d = zeros(len,len); % Covariance matrix for reduced model with sigma_d=0 (needed for LRT). iSig_no_d = eye(len,len); % Inverse of covariance matrix Sig_no_c = zeros(len,len); % Covariance matrix for reduced model with sigma_c=0 (needed for LRT). iSig_no_c = eye(len,len); % Inverse of covariance matrix if arorder > 0 Phi = zeros(sub,arorder); get_ar % updates Phi, Sigma, and -> V (from Sigma) else get_V_no_ar end ystar = zeros(n_G, 1); % Sums of squared residuals, concatenated across Ss ystar_no_c = zeros(n_G, 1); % SSR for reduced model 1 ystar_no_d = zeros(n_G, 1); % SSR for reduced model 2 resid_no_c = resid; % Set temporary values needed for first iteration. resid_no_d = resid; get_ystar; % Fit the variance parameter design matrix to ystar, est. residual variances G = Create_Design_Eq2(x,V); % Create design matrix for variance estimation betastar = G \ ystar; % Estimate variance components betastar(betastar < 0) = 0; % Use max(0,betastar) to ensure nonnegative variance. % Reduced model 1 ind_d(1) = 0; ind_d(3) = 0; tt_d = (ind_d == 1); betastar_no_d = G(:,tt_d) \ ystar; % Estimate variance components when sigma_d=0 betastar_no_d(betastar_no_d < 0) = 0; % Use max(0,betastar) to ensure nonnegative variance. % Reduced model 2 ind_c(2) = 0; ind_c(3) = 0; tt_c = (ind_c == 1); betastar_no_c = G(:,tt_c) \ ystar; % Estimate variance components when sigma_c=0 betastar_no_c(betastar_no_c < 0) = 0; % Use max(0,betastar) to ensure nonnegative variance. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % Step 2: Iterate % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% iterations = 0; min_change = betastar * epsilon; isconverged = 0; while (iterations < num_iter) && ~isconverged num = size(G,1) / sub; for s = 1:sub Sig1 = ivech(G(((s-1) * num + 1):(s * num),:) * betastar); Sig1 = Sig1 + tril(Sig1,-1)'; wh = ((s-1)*T+1):(s*T); % which indices in Cov mtx for this subject Sig(wh, wh) = Sig1; iSig(wh, wh) = inv(Sig(wh, wh)); % Cov mtx when sigma_d =0 Sig1_no_d = ivech(G(((s-1) * num + 1):(s * num),tt_d) * betastar_no_d); Sig1_no_d = Sig1_no_d + tril(Sig1_no_d,-1)'; Sig_no_d(wh, wh) = Sig1_no_d; iSig_no_d(wh, wh) = inv(Sig_no_d(wh, wh)); % Cov mtx when sigma_c =0 Sig1_no_c = ivech(G(((s-1) * num + 1):(s * num),tt_c) * betastar_no_c); Sig1_no_c = Sig1_no_c + tril(Sig1_no_c,-1)'; Sig_no_c(wh, wh) = Sig1_no_c; iSig_no_c(wh, wh) = inv(Sig_no_c(wh, wh)); end beta = inv(D'*iSig*D)*D'*iSig*z; % Beta values resid = z - D*beta; % Residuals beta_no_d = inv(D'*iSig_no_d*D)*D'*iSig_no_d*z; % Beta values when sigma_d=0 resid_no_d = z - D*beta_no_d; % Residuals beta_no_c = inv(D'*iSig_no_c*D)*D'*iSig_no_c*z; % Beta values when sigma_c=0 resid_no_c = z - D*beta_no_c; % Residuals betastar_old = betastar; get_ystar; beta_indiv = get_indiv_betas; % params x subjects matrix of betas betastar = G \ ystar; betastar(betastar < 0) = 0; % Use max(0,betastar) to ensure nonnegative variance. betastar_no_d = G(:,tt_d) \ ystar_no_d; betastar_no_d(betastar_no_d < 0) = 0; % Use max(0,betastar) to ensure nonnegative variance. betastar_no_c = G(:,tt_c) \ ystar_no_c; betastar_no_c(betastar_no_c < 0) = 0; % Use max(0,betastar) to ensure nonnegative variance. isconverged = ~any(abs(betastar - betastar_old) > abs(min_change)); iterations = iterations + 1; end Cov_beta = inv(D'*iSig*D); %W = (Sigma - D*inv(D'*iSigma*D)*D'); % Residual inducing matrix x Sigma df_beta = sub - 1; % this should be sub - q, # params in Xg, but we haven't added this flexibility yet %df_beta = (trace(W).^2)./trace(W*W); % Satterthwaite approximation for degrees of freedom Cov_betastar = inv(G'*G); df_betastar = sub - 1; Sigma = betastar(4:end); yhat = G*betastar; e = ystar - yhat; tau = e'*e/(size(G,1)-size(G,2)); %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Likelihood ratio tests %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% detmat = zeros(sub,1); detmat_no_d = zeros(sub,1); detmat_no_c = zeros(sub,1); detDSigD = 0; detDSigD_no_d = 0; detDSigD_no_c = 0; for k=1:sub, wh = ((k-1) * T + 1):(k * T); detmat(k) = abs(det(Sig(wh,wh))); detmat_no_d(k) = abs(det(Sig_no_d(wh,wh))); detmat_no_c(k) = abs(det(Sig_no_c(wh,wh))); detDSigD = detDSigD+abs(det(D(wh,:)' * iSig(wh,wh) * D(wh,:))); detDSigD_no_d = detDSigD_no_d+abs(det(D(wh,:)' * iSig_no_d(wh,wh) * D(wh,:))); detDSigD_no_c = detDSigD_no_c+abs(det(D(wh,:)' * iSig_no_c(wh,wh) * D(wh,:))); end; % detDSigD = abs(det(D' * iSig * D)); % detDSigD_no_d = abs(det(D' * iSig_no_d * D)); % detDSigD_no_c = abs(det(D' * iSig_no_c * D)); % % Test H0: sigma_d =0 LLd = - 0.5*( sum(log(detmat_no_d))-sum(log(detmat)) + (resid_no_d'* iSig_no_d * resid_no_d - resid'* iSig * resid)); if (type == 'r'), LLd = -0.5*(log(detDSigD_no_d) - log(detDSigD) + sum(log(detmat_no_d))-sum(log(detmat)) +... (resid'* iSig_no_d * resid - resid'* iSig * resid)); end; LRd = max(-2*LLd,0); % randvariance_d = 1-chi2cdf(LRd,1); %Make it a 50:50 mixture V = 0.5*(chi2rnd(1,1000000,1)) + 0.5*(chi2rnd(2,1000000,1)); randvariance_d = mean(V>LRd); % Test H0: sigma_c =0 LLc = - 0.5*( sum(log(detmat_no_c))-sum(log(detmat)) + (resid_no_c'* iSig_no_c * resid_no_c - resid'* iSig * resid)); if (type == 'r'), LLc = -0.5*(log(detDSigD_no_c) - log(detDSigD) + sum(log(detmat_no_c))-sum(log(detmat)) +... (resid'* iSig_no_c * resid - resid'* iSig * resid)); end; LRc = max(-2*LLc,0); % randvariance_c = 1-chi2cdf(2*LRc,1); randvariance_c = mean(V>LRc); betastar = betastar(1:2); % Remove within subject variance Cov_betastar = Cov_betastar(1:2,1:2); % Remove within subject variance % Include covariates in output if (docovariate == 1) if (level == 2) Cov_beta = blkdiag(Cov_beta, Cov_beta); beta = [beta; beta_c]; else Cov_beta = blkdiag(Cov_beta, Cov_beta_c); beta = [beta; beta_c]; end end c2 = clock; elapsed_time = etime(c2, c1); % save output structure names = {'Y1'}; % later; varnames = {'analysisname', 'names', 'beta_names', 'type', 'num_iter', 'epsilon', 'arorder', 'within', 'y', 'x'}; inputOptions = create_struct(varnames); out = struct('analysisname', analysisname, 'beta', beta, 'betastar', betastar, 'beta_indiv', beta_indiv, 'beta_names', {beta_names}, ... 'Cov_beta', Cov_beta, 'Cov_betastar', Cov_betastar, ... 'Sigma', Sigma, 'Phi', Phi, ... 'type', type, 'arorder', arorder, 'isconverged', isconverged, ... 'num_obs', T, 'sub', sub, 'num_iter', num_iter, 'epsilon', epsilon, ... 'iterations', iterations, 'elapsed_time', elapsed_time, 'inputOptions', inputOptions); % save stats out.ste = sqrt(diag(out.Cov_beta)); out.t = out.beta ./ out.ste; out.df_beta = df_beta; out.p = 2 * (1 - tcdf(abs(out.t), out.df_beta)); % two-tailed out.p(out.p == 0) = eps; out.p_tails = 'two-tailed'; out.t_randvariance = out.betastar ./ sqrt(diag(out.Cov_betastar)); out.df_betastar = df_betastar; out.p_randvariance = (1 - tcdf(abs(out.t_randvariance), out.df_betastar)); % one-tailed out.p_randvariance(out.p_randvariance == 0) = eps; out.p_randvariance_tails = 'one-tailed'; out.LRT = [LRd; LRc]; out.pLRT_randvariance = [randvariance_d; randvariance_c]; if strcmp(doplot, 'all') plot_igls_matrices igls_plot_slopes(out, x); elseif strcmp(doplot, 'slopes') igls_plot_slopes(out, x); end % print output if doverbose print_output_text(out) end % _________________________________________________________________________ % % % % * Inline (nested) functions % % % %__________________________________________________________________________ function newstruct = create_struct(varnames) newstruct = struct(); for i = 1:length(varnames) eval(['newstruct.(varnames{i}) = ' varnames{i} ';']); end end function [beta_indiv] = get_indiv_betas % beta_indiv is params x subjects matrix % Reexpress design matrix depending on whether or not the covariate % is applied to the 1st or second level. DD = D; if (level == 2) DD = DD(:,1:(end-1)); %Remove second level covariate from design matrix end beta_indiv = zeros(size(DD,2),sub); for k = 1:sub % Estimate AR parameters using the Yule-Walker method for each subject. wh = ((k-1) * T + 1):(k * T); beta_indiv(:,k) = inv(DD(wh,:)' * iSig(wh,wh) * DD(wh,:)) * DD(wh,:)' * iSig(wh,wh) * z(wh, 1); sigma_indiv(k) = mean(diag(Sig(wh, wh))); %% for plotting: need weight estimates for each subject end end function get_V_no_ar mysig = vech(eye(T)); for k = 1:sub wh = ((k-1) * T2 + 1):(k * T2); % indices in time series for this subject if (strcmp(within, 'common')) V(wh) = mysig; % Create one regressor for common variance else V(wh,k) = mysig; % Cretae m regressors otherwise end end end function get_ar % % beta_indiv = zeros(size(D,2),sub); for k = 1:sub % Estimate AR parameters using the Yule-Walker method for each subject. wh = ((k-1) * T + 1):(k * T); % % beta_indiv(:,k) = (D(wh,:) \ z(wh)); % % res = z(wh) - (D(wh,:) * beta_indiv(:,k)); [a,e] = aryule(resid(wh), arorder); % Yule-Walker Phi(k,:) = a(2:(arorder+1)); % Parameters of AR(p) model Sigma(k) = sqrt(e); % standard deviation of AR(p) model; ***not used?*** % Find the covariance matrix A = diag(ones(T,1)); for j=1:arorder A = A + diag(Phi(k,j)*ones((T-j),1),-j); end wh = ((k-1) * T2 + 1):(k * T2); % indices in time series for this subject iA = inv(A); tmp = vech(iA * iA'); if strcmp(within, 'common') % V(wh) = Sigma(k)^2 * tmp; % Covariance function in vech format V(wh) = tmp; % Covariance function in vech format else % V(wh,k) = Sigma(k)^2 * tmp; % Covariance function in vech format V(wh,k) = tmp; % Covariance function in vech format end end end function get_ystar if (type == 'i') % IGLS for i=1:sub wh = ((i-1) * T + 1):(i * T); % indices in time series for this subject myresid = resid( wh ); % residuals for this subject. myresid_no_c = resid_no_c(wh ); myresid_no_d = resid_no_d(wh ); tmp = vech(myresid * myresid'); % Find vech of estimated covariance tmp_no_c = vech(myresid_no_c * myresid_no_c'); tmp_no_d = vech(myresid_no_d * myresid_no_d'); wh = ((i-1) * T2 + 1):(i * T2); % indices in time series for this subject ystar(wh) = tmp; ystar_no_c(wh) = tmp_no_c; ystar_no_d(wh) = tmp_no_d; end elseif (type == 'r') % RIGLS DD = D; if (level == 2) DD = DD(:,1:(end-1)); %Remove second level covariate from design matrix end for i=1:sub wh = ((i-1) * T + 1):(i*T); % indices in time series for this subject Dtmp = DD(wh, :); rtmp = resid(wh); % residuals for this subject. rtmp_no_c = resid_no_c(wh); rtmp_no_d = resid_no_d(wh); wh = ((i-1) * T2 + 1):(i * T2); Vtmp = V(wh); U = ivech(Vtmp); iU = inv(U); iU_no_c = iU; iU_no_d = iU; % iU = iSig(wh,wh); % iU_no_c = iSig_no_c(wh,wh); % iU_no_d = iSig_no_d(wh,wh); rig = rtmp * rtmp' + Dtmp * inv(Dtmp' * iU * Dtmp) * Dtmp'; tmp = vech(rig); % Find vech of estimated covariance rig_no_c = rtmp_no_c * rtmp_no_c' + Dtmp * inv(Dtmp' * iU_no_c * Dtmp) * Dtmp'; tmp_no_c = vech(rig_no_c); % Find vech of estimated covariance rig_no_d = rtmp_no_d * rtmp_no_d' + Dtmp * inv(Dtmp' * iU_no_d * Dtmp) * Dtmp'; tmp_no_d = vech(rig_no_d); % Find vech of estimated covariance wh = ((i-1) * T2 + 1):(i * T2); % indices in time series for this subject ystar(wh) = tmp; ystar_no_c(wh) = tmp_no_c; ystar_no_d(wh) = tmp_no_d; end end end function plot_igls_matrices % Ystar_mtx: Estimate of total covariance, var(ksi) Ystar_mtx = single(resid * resid'); figh = create_figure(['IGLS Error Matrix Plot'], 2, 4); for i = 1:7, subplot(2, 4, i); set(gca,'YDir', 'reverse'); end subplot(2, 4, 8); axis off titles = {'Cov(ksi) est.' 'Random intercept' 'Random slope' 'Within error' 'Fitted' 'residual'}; subplot(2, 4, 1); s = std(Ystar_mtx(:)); clims = [-3 * s 3 * s]; imagesc(Ystar_mtx, clims) title(titles{1}) xlabel('T * N'); ylabel('T * N'); axis image text(T*sub + .01*T*sub, T*sub/2, '=','FontSize', 64) % set color map; make zero values white cm = zeros(256, 3); cvec = abs(linspace(clims(1), clims(2), size(cm, 1))); wh = find(cvec == min(cvec)); wh = wh(1); hotpart = [linspace(.9, 1, length(cvec) - wh + 1)' linspace(1, 0, length(cvec) - wh + 1)' linspace(0, 0, length(cvec) - wh + 1)']; cm(wh:end, :) = hotpart(end:-1:1, :); coolpart = [linspace(0, 0, wh - 1)' linspace(0, 1, wh - 1)' linspace(1, .9, wh - 1)']; cm(1:wh - 1, :) = coolpart; cm(wh-1:wh+1, :) = repmat([.3 .3 .3], 3, 1); colormap(cm) drawnow num_var_comps = size(G, 2) - length(Sigma); % GGmtx: Cell array of matrix error covariance components % ----------------------------------------- myest = [betastar; Sigma]; GGfit = sparse(zeros(T*sub, T*sub)); % get var. component matrices, not including Sigma (within error) for k = 1:num_var_comps for i = 1:sub wh = ((i-1) * T2 + 1):(i * T2); % indices in time series for this subject GG{i} = ivech(G(wh, k)); end % fitted for this component GGmtx{k} = sparse(blkdiag(GG{:})) .* myest(k); GGmtx{k} = full_from_ltr( GGmtx{k} ); % full matrix form from lower triangle GGfit = GGfit + GGmtx{k}; end % now get the one for sigma (V) % ----------------------------------------- wh_is_V = num_var_comps + 1; GGmtx{wh_is_V} = sparse(zeros(T * sub, T * sub)); for k = 1:length(Sigma) for i = 1:sub wh = ((i-1) * T2 + 1):(i * T2); % indices in time series for this subject GG{i} = ivech(G(wh, num_var_comps + k)); end % fitted error GGmtx{wh_is_V} = GGmtx{wh_is_V} + sparse(blkdiag(GG{:})) .* myest(num_var_comps + k); end GGmtx{wh_is_V} = full_from_ltr( GGmtx{wh_is_V} ); % full matrix form from lower triangle GGfit = GGfit + GGmtx{wh_is_V}; % image them % ----------------------------------------- for k = 1:num_var_comps + 1 subplot(2, 4, k + 1); imagesc(GGmtx{k}, clims); title(sprintf('%s\nb-hat = %3.3f', titles{k+1}, myest(k))); axis image if k <= num_var_comps, text(T*sub + .01*T*sub, T*sub/2, '+','FontSize', 64), end drawnow end subplot(2, 4, num_var_comps + 3); imagesc(GGfit, clims) title(titles{5}) axis image drawnow residmtx = double(Ystar_mtx) - GGfit; clear GGfit fprintf('MST (diag of Ystar_mtx): %3.5f\n', mean(diag(Ystar_mtx))); fprintf('MSE (diag of resid): %3.5f\n', mean(diag(residmtx))); axh = subplot(2, 4, num_var_comps + 4); imagesc(residmtx, clims) title(titles{6}) axis image drawnow axh2 = subplot(2, 4, num_var_comps + 5); axes(axh2) imagesc(residmtx, clims) colorbar; %('peer', axh); set(axh2,'Visible','off') end end % END MAIN FUNCTION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % Subfunctions % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% function G = Create_Design_Eq2(x,V) % function G = Create_Design(x) % % Create Design matrix for estimation of variance components % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% [T, sub] = size(x); T2 = T * (T+1) / 2; one = zeros(T,1) + 1; G = zeros(sub * T2, 3); for i = 1:sub wh = ((i-1) * T2 + 1):(i * T2); G(wh,1) = vech(one*one'); % Regressor corresponding to sigma_d^2 G(wh,2) = vech(x(:,i) * x(:,i)'); % Regressor corresponding to sigma_c^2 G(wh,3) = vech(one * x(:,i)' + x(:,i) * one'); % Regressor corresponding to sigma_dc end G = [G V]; % use within-subject covariance as a regressor end %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% function V = vech(Mat) % function V = vech(Mat) % % Calculate vech for the matrix Mat % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% V = Mat(tril(true(size(Mat)))); end %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% function Mat = ivech(V) % function Mat = vech(V) % % Calculate the "inverse" of the vech function % This is much faster than matlab's squareform.m % It could be speeded up, probably, by operating column-wise % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% len = length(V); dim = -0.5 + sqrt(0.25 + 2 * len); Mat = zeros(dim, dim); ind=1; for i=1:dim for j=i:dim Mat(j,i) = V(ind); ind = ind+1; end end end %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% function print_output_text(out) fprintf('\nigls.m output report:\n---------------------------------------\n'); fprintf('Data: %3.0f observations x %3.0f subjects \n', out.num_obs, out.sub); typestr = {'igls' 'rigls'}; nystr = {'No' 'Yes'}; fprintf('Fit Type: %s\n', typestr{strcmp(out.type, 'r') + 1}); fprintf('AR(p) model: %s', nystr{(out.arorder > 0) + 1}); if out.arorder > 0 fprintf(', AR(%d)\n', out.arorder); else fprintf('\n'); end fprintf('Converged: %s\n', nystr{out.isconverged + 1}); fprintf('Max iterations: %3.0f, Completed iterations: %3.0f\n', out.num_iter, out.iterations); fprintf('Epsilon for convergence: %3.6f\n', out.epsilon); fprintf('Elapsed time: %3.2f s\n', out.elapsed_time); fprintf('\nStatistics: Tests of inference on fixed population parameters\n') fprintf('Parameter\test.\tt(%3.0f)\tp\t\n', out.df_beta) sigstring = {' ' '+' '*' '**' '***'}; for i = 1:length(out.beta) % if i == 1, name = 'Intcpt.'; else name = ['Pred' num2str(i - 1)]; end % names input above, at start sig = out.p(i) < [Inf .1 .05 .01 .001]; fprintf('%s\t%3.3f\t%3.2f\t%3.6f\t%s\n', out.beta_names{i}, out.beta(i), out.t(i), out.p(i), sigstring{find(sig, 1, 'last')}) end fprintf('\n\nStatistics: Tests of significance on random effects (variances)\n') % fprintf('Parameter\test.\tt(%3.0f)\tp\t\n', out.df_betastar) fprintf('Parameter\test.\tLRT\tp\t\n') sigstring = {' ' '+' '*' '**' '***'}; for i = 1:length(out.beta) %if i == 1, name = 'Intcpt.'; else name = ['Pred' num2str(i - 1)]; end if (~strcmp(out.beta_names(i),'Covariate_intcpt') && ~strcmp(out.beta_names(i),'Covariate_slope')) % sig = out.p_randvariance(i) < [Inf .1 .05 .01 .001]; % fprintf('%s\t%3.3f\t%3.2f\t%3.6f\t%s\n', out.beta_names{i}, out.betastar(i), out.t_randvariance(i), out.p_randvariance(i), sigstring{find(sig, 1, 'last')}) if i <= length(out.pLRT_randvariance) sig = out.pLRT_randvariance(i) < [Inf .1 .05 .01 .001]; fprintf('%s\t%3.3f\t%3.2f\t%3.6f\t%s\n', out.beta_names{i}, out.betastar(i), out.LRT(i), out.pLRT_randvariance(i), sigstring{find(sig, 1, 'last')}) end end end fprintf('\n---------------------------------------\n\n'); end %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% function M = full_from_ltr(M) tmp2 = M; tmp2 = ( tmp2 .* (1 - eye(size(tmp2))) )'; M = M + tmp2; end % This duplicates a function in the SCN Core toolbox, but is included to % make igls.m stand-alone function f1 = create_figure(tagname, varargin) % f1 = create_figure(['tagname'], [subplotrows], [subplotcols], [do not clear flag]) % % checks for old figure with tag of tagname % clears it if it exists, or creates new one if it doesn't if nargin < 1 || isempty(tagname) tagname = 'nmdsfig'; end doclear = 1; % clear if new or if old and existing if length(varargin) > 2 && varargin{3} % use same figure; do not clear doclear = 0; end old = findobj('Tag', tagname); if ~isempty(old) if doclear, clf(old); end f1 = old; else % Or create new scnsize = get(0,'ScreenSize'); xdim = min(scnsize(3)./2, 700); ydim = min(scnsize(4)./2, 700); f1 = figure('position',round([50 50 xdim ydim]),'color','white'); set(f1, 'Tag', tagname, 'Name', tagname); end % activate this figure figure(f1); if doclear % true for new figs or cleared ones % Create subplots, if requested; set axis font sizes if length(varargin) > 0 i = max(1, varargin{1}); j = max(1, varargin{2}); else i = 1; j = 1; end np = max(1, i * j); for k = 1:np axh(k) = subplot(i,j,k); set(gca,'FontSize',18),hold on end axes(axh(1)); end end function [y, x] = cell2matrix(y, x) % one or none of y and x can be cell arrays if ~iscell(x) && ~iscell(y) return end if iscell(y) if ~iscell(x), error('y and x must either both be cell arrays or neither can be.'); end sz = zeros(size(y, 1), 2); for i = 1:length(y) sz(i, :) = size(y{i}); end sz = sz(:, 1); if all(sz) == sz(1) % all the same? else mx = max(sz(:, 1)); % longest for i = 1:length(y) if sz(i) < mx mn = mean(y{i}); mnx = mean(x{i}); y{i} = [y{i}; mn(ones(mx - sz(i), 1))]; % assume y and x match and are both cells x{i} = [x{i}; mnx(ones(mx - sz(i), 1))]; end end end %turn into matrix y = cell2mat(y); x = cell2mat(x); end end \ No newline at end of file diff --git a/CanlabCore/Statistics_tools/classify_naive_bayes.m b/CanlabCore/Statistics_tools/classify_naive_bayes.m index 2535746d..66a94d63 100644 --- a/CanlabCore/Statistics_tools/classify_naive_bayes.m +++ b/CanlabCore/Statistics_tools/classify_naive_bayes.m @@ -689,20 +689,10 @@ switch spm('Ver') case 'SPM2' - % spm_defaults is a script + % SPM2: spm_defaults is a script, not callable here disp('WARNING: spm defaults not set for spm2. Make sure your defaults are set correctly'); - - case 'SPM5' - % spm_defaults is a function - spm_defaults() - - case 'SPM8' - % spm_defaults is a function - spm_defaults() - otherwise - % unknown SPM - disp('Unknown version of SPM!'); + % SPM5+, including any future versions spm_defaults() end diff --git a/CanlabCore/Statistics_tools/classify_naive_bayes_2010.m b/CanlabCore/Statistics_tools/classify_naive_bayes_2010.m index 8c4d4934..fa434c8f 100644 --- a/CanlabCore/Statistics_tools/classify_naive_bayes_2010.m +++ b/CanlabCore/Statistics_tools/classify_naive_bayes_2010.m @@ -699,20 +699,10 @@ switch spm('Ver') case 'SPM2' - % spm_defaults is a script + % SPM2: spm_defaults is a script, not callable here disp('WARNING: spm defaults not set for spm2. Make sure your defaults are set correctly'); - - case 'SPM5' - % spm_defaults is a function - spm_defaults() - - case 'SPM8' - % spm_defaults is a function - spm_defaults() - otherwise - % unknown SPM - disp('Unknown version of SPM!'); + % SPM5+, including any future versions spm_defaults() end diff --git a/CanlabCore/Unit_tests/README.md b/CanlabCore/Unit_tests/README.md new file mode 100644 index 00000000..c1862e91 --- /dev/null +++ b/CanlabCore/Unit_tests/README.md @@ -0,0 +1,116 @@ +# CanlabCore unit tests + +This is the unit-test suite for CanlabCore. It uses MATLAB's built-in +`matlab.unittest` framework. There is no external runner — you can run any +single test by `runtests('canlab_test_constructor')`, or the whole suite +with `canlab_run_all_tests`. + +Test files (and the runner and shared helpers) are namespaced with a +`canlab_` prefix to avoid colliding with similarly-named files from other +toolboxes a user may have on their MATLAB path. + +## Running locally + +```matlab +cd CanlabCore/Unit_tests +results = canlab_run_all_tests; % unit suite (default) +results = canlab_run_all_tests('Tag', 'Core'); % only tagged 'Core' +results = canlab_run_all_tests('JUnit', 'results.xml'); % also write JUnit XML +results = canlab_run_all_tests('Walkthroughs', 'only'); % run only walkthrough tests +results = canlab_run_all_tests('Walkthroughs', 'include'); % unit + walkthrough +``` + +`canlab_run_all_tests` adds the parent `CanlabCore/` tree to the MATLAB +path with subfolders, so you do not need to set up paths first. SPM and +Neuroimaging_Pattern_Masks must already be on your path if a test +requires them; tests that need them call `assumeNotEmpty(which('...'))` +and skip gracefully when absent. + +## Layout + +``` +Unit_tests/ +├── canlab_run_all_tests.m entry point used locally and in CI +├── helpers/ shared fixtures +│ └── canlab_get_sample_fmri_data.m +├── fmri_data/ tests of the @fmri_data class +├── image_vector/ tests of @image_vector behavior shared by subclasses +├── statistic_image/ tests of @statistic_image (ttest, threshold) +├── region/ tests of @region construction and methods +├── atlas/ tests of @atlas +├── workflows/ end-to-end pipeline smoke tests +├── walkthroughs/ Tier B integration tests (canlab_help_* scripts) +└── old_to_integrate/ legacy ad-hoc scripts pending rewrite as proper tests +``` + +`old_to_integrate/` is excluded from discovery by `canlab_run_all_tests`. +Files there should be ported into one of the other folders as proper +`matlab.unittest` tests (and renamed to `canlab_test_*.m`) over time. + +`walkthroughs/` contains thin wrappers around the `canlab_help_*` +walkthrough scripts that live in the +[CANlab_help_examples](https://github.com/canlab/CANlab_help_examples) +repository. Each wrapper provides a `WorkingFolderFixture` (clean cwd) +and `close all force` setup, then evals the corresponding help script +under `evalc`. Reaching the end of the script counts as a pass; we don't +verify pixel content or numerical results. These are slower and depend +on `CANlab_help_examples` being on the MATLAB path, so they are +**skipped by default** and run on a nightly schedule via +`.github/workflows/tests-walkthroughs.yml`. Pass `'Walkthroughs', 'only'` +or `'Walkthroughs', 'include'` to `canlab_run_all_tests` to opt in. + +## Writing a new test + +Function-based tests are the default style. A test file is a function +named `canlab_test_.m` that declares its sub-tests: + +```matlab +function tests = canlab_test_thing +tests = functiontests(localfunctions); +end + +function test_some_specific_behavior(tc) + obj = canlab_get_sample_fmri_data(); + result = method_under_test(obj); + tc.verifyEqual(size(result.dat), size(obj.dat)); +end +``` + +Conventions: + +- One file per method or invariant. File name `canlab_test_.m`, + outer function name matching the file. Inner sub-test function names + follow the matlab.unittest convention `test_` — + they are local to the file and don't need the `canlab_` prefix. +- Use `tc.verify*` (records failure, keeps going) for normal assertions. + Reserve `tc.assert*` for setup preconditions where the rest of the + test cannot run. +- Use `tc.assumeNotEmpty(which('foo.nii'))` when a test requires a file + that may not be on every contributor's path. The test will be marked + *Incomplete* rather than failed. +- Prefer `canlab_get_sample_fmri_data()` (in `helpers/`) over re-loading + the sample directly so the sample-load path is centralized. +- A test should not write outside a temp directory. If you need to write, + use `tempname` and clean up with `tc.addTeardown(@delete, ...)`. +- Tests should not depend on each other or on execution order. + +### Why `canlab_test_*` files instead of `test_*`? + +`matlab.unittest`'s `TestSuite.fromFolder` only auto-discovers files +whose names start or end with `test` (case-insensitive), so the runner +glob-discovers `canlab_test_*.m` and uses `TestSuite.fromFile` directly. +The result is the same; the difference is that `canlab_test_constructor` +on the path won't shadow some other toolbox's `test_constructor`. + +### Tags + +Apply tags via class-based tests when a group of tests needs to be +selectable as a unit (e.g., `'RequiresMasks'`, `'Slow'`, `'GUI'`). +Tags filter via `canlab_run_all_tests('Tag', 'Slow')` or +`canlab_run_all_tests('Tag', '~GUI')`. + +## CI + +Every push and PR runs the suite on the latest supported MATLAB release +on Ubuntu. SPM and Neuroimaging_Pattern_Masks are checked out as +siblings. See `.github/workflows/test.yml` for the full configuration. diff --git a/CanlabCore/Unit_tests/atlas/canlab_test_atlas_constructor.m b/CanlabCore/Unit_tests/atlas/canlab_test_atlas_constructor.m new file mode 100644 index 00000000..2492cdbb --- /dev/null +++ b/CanlabCore/Unit_tests/atlas/canlab_test_atlas_constructor.m @@ -0,0 +1,10 @@ +function tests = canlab_test_atlas_constructor +%TEST_ATLAS_CONSTRUCTOR Smoke test for atlas() with no external files. +tests = functiontests(localfunctions); +end + + +function test_empty_atlas_constructor(tc) +atl = atlas(); +tc.verifyClass(atl, 'atlas'); +end diff --git a/CanlabCore/Unit_tests/canlab_run_all_tests.m b/CanlabCore/Unit_tests/canlab_run_all_tests.m new file mode 100644 index 00000000..2257e139 --- /dev/null +++ b/CanlabCore/Unit_tests/canlab_run_all_tests.m @@ -0,0 +1,100 @@ +function results = canlab_run_all_tests(varargin) +%CANLAB_RUN_ALL_TESTS Run the CanlabCore unit test suite. +% +% results = canlab_run_all_tests +% results = canlab_run_all_tests('JUnit', 'test-results.xml') +% results = canlab_run_all_tests('Tag', 'Core') % run only tagged tests +% results = canlab_run_all_tests('Tag', '~RequiresMasks') % skip a tag +% results = canlab_run_all_tests('Walkthroughs', 'only') % only walkthrough tests +% results = canlab_run_all_tests('Walkthroughs', 'include') % unit + walkthrough +% +% Discovers test files matching canlab_test_*.m anywhere under this +% folder (excluding old_to_integrate/) and runs them via matlab.unittest. +% Adds the parent CanlabCore tree to the path before running. Returns a +% matlab.unittest.TestResult array suitable for both interactive use and +% CI; in CI, throw on failure with assertSuccess(results). +% +% Walkthrough integration tests live under walkthroughs/ and are +% slow (they run end-to-end help scripts from the CANlab_help_examples +% repo). They are SKIPPED by default. Pass 'Walkthroughs', 'only' to run +% only walkthroughs, or 'Walkthroughs', 'include' to run both tiers. +% +% Note on filename pattern: test files are namespaced as canlab_test_* +% to avoid colliding with other toolboxes a user might have on their +% path. matlab.unittest's TestSuite.fromFolder only auto-discovers +% files whose names start or end with "test", so this runner does its +% own glob and uses TestSuite.fromFile on each match. + +p = inputParser; +p.addParameter('JUnit', '', @(x) ischar(x) || isstring(x)); +p.addParameter('Tag', '', @(x) ischar(x) || isstring(x)); +p.addParameter('Walkthroughs', 'skip', @(x) any(strcmpi(x, {'skip', 'only', 'include'}))); +p.parse(varargin{:}); + +import matlab.unittest.TestSuite +import matlab.unittest.TestRunner +import matlab.unittest.plugins.XMLPlugin +import matlab.unittest.selectors.HasTag +import matlab.unittest.Verbosity + +this_dir = fileparts(mfilename('fullpath')); +canlabcore_root = fileparts(this_dir); +addpath(genpath(canlabcore_root)); + +% Glob for canlab_test_*.m files; skip old_to_integrate/. +% Note: TestSuite.fromFile returns matlab.unittest.Test arrays (Test is a +% concrete subclass of TestSuite), so we initialize with Test.empty rather +% than TestSuite.empty — mixing the abstract empty with concrete Tests +% errors during horzcat. +walkthrough_mode = lower(char(p.Results.Walkthroughs)); +matches = dir(fullfile(this_dir, '**', 'canlab_test_*.m')); +suite = matlab.unittest.Test.empty; +for k = 1:numel(matches) + fpath = fullfile(matches(k).folder, matches(k).name); + if contains(fpath, [filesep 'old_to_integrate' filesep]) + continue + end + is_walkthrough = contains(fpath, [filesep 'walkthroughs' filesep]); + switch walkthrough_mode + case 'skip' + if is_walkthrough, continue, end + case 'only' + if ~is_walkthrough, continue, end + case 'include' + % run everything + end + suite = [suite, TestSuite.fromFile(fpath)]; %#ok +end + +tag = char(p.Results.Tag); +if ~isempty(tag) + if startsWith(tag, '~') + suite = suite.selectIf(~HasTag(tag(2:end))); + else + suite = suite.selectIf(HasTag(tag)); + end +end + +runner = TestRunner.withTextOutput('OutputDetail', Verbosity.Concise); + +junit_path = char(p.Results.JUnit); +if ~isempty(junit_path) + junit_dir = fileparts(junit_path); + if ~isempty(junit_dir) && ~exist(junit_dir, 'dir') + mkdir(junit_dir); + end + runner.addPlugin(XMLPlugin.producingJUnitFormat(junit_path)); +end + +results = runner.run(suite); + +n_passed = sum([results.Passed]); +n_failed = sum([results.Failed]); +n_incomplete = sum([results.Incomplete]); +fprintf('\n=== %d passed, %d failed, %d incomplete ===\n', ... + n_passed, n_failed, n_incomplete); + +if nargout == 0 + clear results +end +end diff --git a/CanlabCore/Unit_tests/canlab_test_help_examples.m b/CanlabCore/Unit_tests/canlab_test_help_examples.m new file mode 100644 index 00000000..0b41ccc7 --- /dev/null +++ b/CanlabCore/Unit_tests/canlab_test_help_examples.m @@ -0,0 +1,490 @@ +function tests = canlab_test_help_examples +%CANLAB_TEST_HELP_EXAMPLES Smoke-test the docs/ "Quick example" code blocks. +% +% These tests mirror, one-for-one, the runnable code examples shown on +% the per-method documentation pages under CanlabCore/docs/. They are +% smoke tests: a test passes if the example runs end-to-end without +% erroring (and any figures created are cleaned up). We do NOT +% pixel-compare against the docs/class_method_pngs/*.png snapshots. +% +% Why these tests exist. The docs Quick examples are the first thing a +% new user sees; if any of them silently bit-rot, the toolbox feels +% broken on first contact. Wiring them into the per-push CI is the +% cheapest possible defense. +% +% Headless graphics. setup() forces DefaultFigureVisible 'off' so +% nothing pops up during interactive runs. Tests that exercise +% genuinely GUI-only code paths (orthviews, surface meshes that need +% OpenGL) wrap the offending call in a try/catch and call +% tc.assumeFail(...) on missing-graphics errors so CI does not fail +% spuriously when run on a machine without a display. +% +% Caching. setupOnce loads the emotionreg sample once and caches the +% group t-test (raw and thresholded) and a region object on +% tc.TestData, so the ~18 tests that need them don't recompute. + +tests = functiontests(localfunctions); +end + + +% ===================================================================== +% Fixtures +% ===================================================================== + +function setupOnce(tc) %#ok<*DEFNU> +% Cache the sample dataset and the derived stat objects that 18+ tests +% want, so we pay the cost once per test run rather than per test. +tc.TestData.imgs = canlab_get_sample_fmri_data(); +t_raw = ttest(tc.TestData.imgs); +tc.TestData.t_raw = t_raw; +t_thr = threshold(t_raw, .005, 'unc', 'k', 10); +tc.TestData.t_thr = t_thr; +tc.TestData.region_thr = region(t_thr); +end + + +function setup(tc) +close all force; +tc.TestData.PrevFigVis = get(0, 'DefaultFigureVisible'); +set(0, 'DefaultFigureVisible', 'off'); +end + + +function teardown(tc) +close all force; +if isfield(tc.TestData, 'PrevFigVis') + set(0, 'DefaultFigureVisible', tc.TestData.PrevFigVis); +end +end + + +% ===================================================================== +% Helpers (local) +% ===================================================================== + +function skip_on_environment_error(tc, ME) +% If an error reflects a missing CI capability (graphics, interactive +% input, or an optional dataset that ships under separate licensing), +% skip the test on this runner instead of failing. Otherwise rethrow. +% +% Categories handled: +% - Graphics: OpenGL / X display / JVM not available (headless runner). +% - Interactive input: e.g. load_atlas('canlab2024') triggers +% bianciardi_create_atlas_obj, which prompts for confirmation when +% atlas source files are not on the path. The runner reports +% 'MATLAB:services:MissingRequiredCapability' for any input() call +% in batch mode. +% - Missing data files: NPS+ signature image +% (weights_NSF_grouppred_cvpcr.img) and the Neurosynth feature set +% (Yarkoni_2013_Neurosynth_featureset1.mat) are required by some +% help examples but are NOT shipped with Neuroimaging_Pattern_Masks +% or CanlabCore on CI checkouts. +gfx_ids = {'MATLAB:graphics:opengl:Unavailable', ... + 'MATLAB:graphics:initialize'}; +input_ids = {'MATLAB:services:MissingRequiredCapability', ... + 'MATLAB:UndefinedFunction'}; +msg = lower(ME.message); + +is_gfx = any(strcmp(ME.identifier, gfx_ids)) || ... + contains(msg, 'opengl') || contains(msg, 'display') || ... + contains(msg, 'java') || contains(msg, 'jvm'); +is_input = any(strcmp(ME.identifier, input_ids)) && ... + contains(msg, 'support for user input'); +% load_image_set / annotate_binary_results_map handle missing files by +% printing "CANNOT FIND IMAGES ..." or "Find and add the file ..." with +% disp() and then aborting via `error('Exiting')` - so ME.message is +% literally "Exiting" or "Exiting." with an empty identifier. +bare_exiting = isempty(ME.identifier) && ... + ismember(strtrim(strrep(ME.message, '.', '')), ... + {'Exiting', 'exiting'}); +is_missing_data = contains(msg, 'cannot find images') || ... + contains(msg, 'find and add the file') || ... + contains(msg, 'not found in matlab path') || ... + contains(msg, 'no such file or directory') || ... + bare_exiting; + +if is_gfx + tc.assumeFail(['needs graphics environment: ' ME.message]); +elseif is_input + tc.assumeFail(['needs interactive input not available in CI: ' ME.message]); +elseif is_missing_data + tc.assumeFail(['needs an optional data file not on the CI path: ' ME.message]); +else + rethrow(ME); +end +end + + +function skip_on_graphics_error(tc, ME) +% Backwards-compatible alias - delegates to the broader environment check. +skip_on_environment_error(tc, ME); +end + + +% ===================================================================== +% PHASE 4A — class-page and stand-alone examples (4 tests) +% ===================================================================== + +function test_atlas_methods_isosurface_montage(tc) +% docs/atlas_methods.md +% load_atlas('canlab2024') pulls in the Bianciardi brainstem atlas, which +% prompts for user input when its (separately-licensed) source files are +% missing - skip on CI rather than fail. +tc.applyFixture(matlab.unittest.fixtures.CurrentFolderFixture(tempdir)); +try + obj = load_atlas('canlab2024'); + create_figure('fig'); isosurface(obj); + view(135, 30); lightFollowView; + create_figure('fig2'); axis off; montage(obj); + tc.verifyNotEmpty(obj.labels); +catch ME + skip_on_environment_error(tc, ME); +end +end + + +function test_atlas_select_atlas_subset(tc) +% docs/individual_functions/atlas_select_atlas_subset.md +% Same Bianciardi prompt issue as the atlas_methods test above. +try + obj = load_atlas('canlab2024'); + thal = select_atlas_subset(obj, {'Thal'}); + tc.verifyNotEmpty(thal.labels); + tc.verifyClass(thal, 'atlas'); + create_figure('fig'); isosurface(thal); +catch ME + skip_on_environment_error(tc, ME); +end +end + + +function test_annotate_binary_results_map(tc) +% docs/individual_functions/fmri_data_annotate_binary_results_map.md +% Requires Yarkoni_2013_Neurosynth_featureset1.mat, not on the CI path. +obj = tc.TestData.imgs; +t = ttest(obj, .005, 'uncorrected'); +t.dat = single(t.dat > 3); +t = fmri_data(t); +try + RESULTS = annotate_binary_results_map(t); + tc.verifyNotEmpty(RESULTS); +catch ME + skip_on_environment_error(tc, ME); +end +end + + +function test_outliers_notimeseries(tc) +% docs/individual_functions/fmri_data_outliers.md +obj = tc.TestData.imgs; +[est_outliers_uncorr, est_outliers_corr, outlier_tables] = ... + outliers(obj, 'notimeseries'); +tc.verifyEqual(numel(est_outliers_uncorr), size(obj.dat, 2)); +tc.verifyEqual(numel(est_outliers_corr), size(obj.dat, 2)); +tc.verifyClass(outlier_tables, 'struct'); +end + + +% ===================================================================== +% PHASE 4B — image_vector / fmri_data methods (10 tests) +% ===================================================================== + +function test_image_similarity_plot(tc) +% docs/individual_functions/fmri_data_image_similarity_plot.md +% Requires the NPS+ signature image (weights_NSF_grouppred_cvpcr.img), +% not on the CI path. +imgs = tc.TestData.imgs; +try + image_similarity_plot(imgs, 'mapset', 'npsplus', 'average'); +catch ME + skip_on_environment_error(tc, ME); +end +end + + +function test_image_similarity_plot_bucknermaps(tc) +% docs/individual_functions/fmri_data_image_similarity_plot_bucknermaps.md +t = tc.TestData.t_raw; +stats = image_similarity_plot_bucknermaps(t); +tc.verifyNotEmpty(stats); +end + + +function test_jackknife_similarity(tc) +% docs/individual_functions/fmri_data_jackknife_similarity.md +imgs = tc.TestData.imgs; +[sim_values, d, low_agreement, Nvox] = jackknife_similarity(imgs, ... + 'similarity_metric', 'correlation'); +tc.verifyEqual(numel(sim_values), size(imgs.dat, 2)); +tc.verifyTrue(isnumeric(d)); +tc.verifyClass(low_agreement, 'logical'); +tc.verifyTrue(all(Nvox > 0)); +end + + +function test_fmri_data_montage(tc) +% docs/individual_functions/fmri_data_montage.md +t = tc.TestData.t_thr; +create_figure('m'); axis off; montage(t); +end + + +function test_fmri_data_orthviews(tc) +% docs/individual_functions/fmri_data_orthviews.md +% orthviews uses SPM's spm_orthviews and requires a Graphics window. +tc.assumeTrue(usejava('jvm') && feature('ShowFigureWindows'), ... + 'orthviews requires an interactive figure window'); +t = tc.TestData.t_thr; +try + orthviews(t); +catch ME + skip_on_graphics_error(tc, ME); +end +end + + +function test_fmri_data_mahal(tc) +% docs/individual_functions/fmri_data_mahal.md +imgs = tc.TestData.imgs; +[ds, expectedds, p_vals, wh_outlier_uncorr, wh_outlier_corr] = mahal(imgs); +tc.verifyEqual(numel(ds), size(imgs.dat, 2)); +tc.verifyEqual(numel(expectedds), size(imgs.dat, 2)); +tc.verifyEqual(numel(p_vals), size(imgs.dat, 2)); +tc.verifyEqual(numel(wh_outlier_uncorr), size(imgs.dat, 2)); +tc.verifyEqual(numel(wh_outlier_corr), size(imgs.dat, 2)); +end + + +function test_fmri_data_pca(tc) +% docs/individual_functions/fmri_data_pca.md +imgs = tc.TestData.imgs; +[scores, eig_obj, explained] = pca(imgs, 'k', 5); +tc.verifyEqual(size(scores, 2), 5); +tc.verifyTrue(isobject(eig_obj)); +tc.verifyTrue(numel(explained) >= 5); +end + + +function test_fmri_data_rmssd_movie(tc) +% docs/individual_functions/fmri_data_rmssd_movie.md +imgs = tc.TestData.imgs; +rmssd_movie(imgs); +end + + +function test_fmri_data_surface(tc) +% docs/individual_functions/fmri_data_surface.md +t = tc.TestData.t_thr; +tc.assumeTrue(usejava('jvm'), 'surface requires Java for rendering'); +try + create_figure('s'); surface(t); +catch ME + skip_on_graphics_error(tc, ME); +end +end + + +function test_fmri_data_wedge_plot_by_atlas(tc) +% docs/individual_functions/fmri_data_wedge_plot_by_atlas.md +% Use yeo17networks - the example in the .md uses this atlas. +t = threshold(tc.TestData.t_raw, .005, 'unc'); +[hh, vals] = wedge_plot_by_atlas(t, 'atlases', {'yeo17networks'}); +tc.verifyNotEmpty(vals); +end + + +% ===================================================================== +% PHASE 4B — statistic_image methods (4 tests) +% ===================================================================== + +function test_statistic_image_riverplot(tc) +% docs/individual_functions/statistic_image_riverplot.md +% load_image_set('npsplus') needs the NPS+ signature images, not on CI. +imgs = tc.TestData.imgs; +try + layer1 = load_image_set('npsplus'); layer1 = get_wh_image(layer1, 1:4); + layer2 = ttest(imgs); layer2 = threshold(layer2, .005, 'unc'); + layer2 = fmri_data(layer2); + layer1.image_names = char({'NPS','SIIPS','GenS','VPS'}); + layer2.image_names = char({'EmoReg group t'}); + riverplot(layer1, 'layer2', layer2); +catch ME + skip_on_environment_error(tc, ME); +end +end + + +function test_statistic_image_multi_threshold(tc) +% docs/individual_functions/statistic_image_multi_threshold.md +% Signature: [o2, dat, sig, pcl, ncl] = multi_threshold(dat, ...) +t = tc.TestData.t_raw; +[o2, dat_out, sig, pcl, ncl] = multi_threshold(t); +tc.verifyClass(o2, 'fmridisplay'); +tc.verifyClass(dat_out, 'statistic_image'); +tc.verifyTrue(iscell(sig) || islogical(sig) || isnumeric(sig)); +tc.verifyTrue(isstruct(pcl) || isobject(pcl) || iscell(pcl) || isempty(pcl)); +tc.verifyTrue(isstruct(ncl) || isobject(ncl) || iscell(ncl) || isempty(ncl)); +end + + +function test_statistic_image_table(tc) +% docs/individual_functions/statistic_image_table.md +t = tc.TestData.t_thr; +[r, results_table] = table(t); +tc.verifyClass(r, 'region'); +tc.verifyClass(results_table, 'table'); +end + + +function test_statistic_image_threshold(tc) +% docs/individual_functions/statistic_image_threshold.md +t = tc.TestData.t_raw; +t = threshold(t, .005, 'unc', 'k', 10); +create_figure('thr'); axis off; montage(t); +tc.verifyClass(t, 'statistic_image'); +end + + +% ===================================================================== +% PHASE 4B — region methods (6 tests) +% ===================================================================== + +function test_region_table(tc) +% docs/individual_functions/region_table.md +r = tc.TestData.region_thr; +table(r); +tc.verifyTrue(numel(r) > 0); +end + + +function test_region_surface(tc) +% docs/individual_functions/region_surface.md +r = tc.TestData.region_thr; +try + create_figure('rs'); surface(r); +catch ME + skip_on_graphics_error(tc, ME); +end +end + + +function test_region_labelled_surface(tc) +% docs/individual_functions/region_labelled_surface.md +r = tc.TestData.region_thr; +try + create_figure('rls'); labelled_surface(r); +catch ME + skip_on_graphics_error(tc, ME); +end +end + + +function test_region_isosurface(tc) +% docs/individual_functions/region_isosurface.md +r = tc.TestData.region_thr; +create_figure('ri'); set(gcf, 'Position', [100 100 900 700]); +isosurface(r); +end + + +function test_region_montage(tc) +% docs/individual_functions/region_montage.md +r = tc.TestData.region_thr; +montage(r, 'regioncenters', 'colormap'); +end + + +function test_region_table_of_atlas_regions_covered(tc) +% docs/individual_functions/region_table_of_atlas_regions_covered.md +% NOTE: the region/ method self-disclaims as broken (the docs page calls +% this out and recommends the @image_vector path). Treat as smoke-only: +% if it errors we mark it as expected-failure rather than a regression. +r = tc.TestData.region_thr; +try + table_of_atlas_regions_covered(r); +catch ME + tc.assumeFail(['region.table_of_atlas_regions_covered ' ... + 'is documented as broken on the @region path: ' ME.message]); +end +end + + +% ===================================================================== +% PHASE 4B — stand-alone visualization helpers (6 tests) +% ===================================================================== + +function test_addbrain(tc) +% docs/individual_functions/addbrain.md +try + create_figure('ab'); set(gcf, 'Position', [100 100 900 700]); + p = addbrain('hires left'); set(p, 'FaceAlpha', .8); + addbrain('thalamus'); + addbrain('bg'); + view(135, 10); lightRestoreSingle; +catch ME + skip_on_graphics_error(tc, ME); +end +end + + +function test_barplot_columns(tc) +% docs/individual_functions/barplot_columns.md +rng(7); +Y = [randn(20,1)+1, randn(20,1)+0.3, randn(20,1)-0.5]; +colors = seaborn_colors(8); +[handles, ~, ~, statstable] = barplot_columns(Y, 'nofig', ... + 'names', {'Cond A', 'Cond B', 'Cond C'}, 'colors', colors(1:3)); +tc.verifyClass(handles, 'struct'); +tc.verifyClass(statstable, 'table'); +tc.verifyEqual(height(statstable), 3); +end + + +function test_cluster_surf(tc) +% docs/individual_functions/cluster_surf.md +r = tc.TestData.region_thr; +try + create_figure('cs'); set(gcf, 'Position', [100 100 1000 700]); + cluster_surf(r, 5, 'left'); +catch ME + skip_on_graphics_error(tc, ME); +end +end + + +function test_image_scatterplot(tc) +% docs/individual_functions/image_scatterplot.md +imgs = tc.TestData.imgs; +% The .md example uses an OLS vs. robust regression comparison; mirror it. +tc.assumeTrue(ismember('Reappraisal_Success', ... + imgs.metadata_table.Properties.VariableNames), ... + 'metadata_table is missing Reappraisal_Success column'); +imgs.X = [imgs.metadata_table.Reappraisal_Success - ... + mean(imgs.metadata_table.Reappraisal_Success), ... + ones(size(imgs.dat, 2), 1)]; +out_ols = regress(imgs, 'noverbose', 'nodisplay'); +out_rob = regress(imgs, 'robust', 'noverbose', 'nodisplay'); +t_ols = get_wh_image(out_ols.t, 1); +t_rob = get_wh_image(out_rob.t, 1); +image_scatterplot(t_ols, t_rob, 'colorpoints'); +xlabel('OLS t-value'); ylabel('Robust regression t-value'); +end + + +function test_canlab_results_fmridisplay(tc) +% docs/individual_functions/canlab_results_fmridisplay.md +t = tc.TestData.t_thr; +o2 = canlab_results_fmridisplay(t); +tc.verifyClass(o2, 'fmridisplay'); +end + + +function test_plot_correlation_matrix(tc) +% docs/individual_functions/plot_correlation_matrix.md +rng(7); +S = toeplitz([1 .6 .3 .1 0 0]); +X = mvnrnd([0 0 0 0 0 0], S, 50); +var_names = {'A' 'B' 'C' 'D' 'E' 'F'}; +OUT = plot_correlation_matrix(X, 'var_names', var_names); +tc.verifyTrue(isstruct(OUT) || isobject(OUT)); +end diff --git a/CanlabCore/Unit_tests/fmri_data/canlab_test_cat_split.m b/CanlabCore/Unit_tests/fmri_data/canlab_test_cat_split.m new file mode 100644 index 00000000..bfde4ebd --- /dev/null +++ b/CanlabCore/Unit_tests/fmri_data/canlab_test_cat_split.m @@ -0,0 +1,54 @@ +function tests = canlab_test_cat_split +%CANLAB_TEST_CAT_SPLIT Basic image math: cat, split, mean, get_wh_image. +% +% Uses the emotionreg sample (30 images) as a fixture and exercises the +% combine/subset operators without going near full-volume computation. + +tests = functiontests(localfunctions); +end + + +function test_cat_doubles_image_count(tc) +% cat([a; a]) along the image axis should give 2x the image count. +obj = canlab_get_sample_fmri_data(); +n = size(obj.dat, 2); +combined = cat(obj, obj); +tc.verifyClass(combined, 'fmri_data'); +tc.verifyEqual(size(combined.dat, 2), 2 * n); +end + + +function test_split_then_cat_round_trip(tc) +% split() splits according to obj.images_per_session, so we set that first +% to two halves and verify cat(split) preserves the original image count. +obj = canlab_get_sample_fmri_data(); +n = size(obj.dat, 2); +half = floor(n / 2); +obj.images_per_session = [half, n - half]; + +halves = split(obj); +tc.verifyEqual(numel(halves), 2); + +% halves is a cell array of fmri_data objects. +rebuilt = cat(halves{1}, halves{2}); +tc.verifyEqual(size(rebuilt.dat, 2), n); +end + + +function test_get_wh_image_returns_subset(tc) +% Pull the first 5 images; .dat should narrow accordingly, voxels unchanged. +obj = canlab_get_sample_fmri_data(); +sub = get_wh_image(obj, 1:5); +tc.verifyClass(sub, 'fmri_data'); +tc.verifyEqual(size(sub.dat, 2), 5); +tc.verifyEqual(size(sub.dat, 1), size(obj.dat, 1)); +end + + +function test_mean_collapses_to_single_image(tc) +% mean across images should produce one image with the same voxel count. +obj = canlab_get_sample_fmri_data(); +m = mean(obj); +tc.verifyEqual(size(m.dat, 2), 1); +tc.verifyEqual(size(m.dat, 1), size(obj.dat, 1)); +end diff --git a/CanlabCore/Unit_tests/fmri_data/canlab_test_constructor.m b/CanlabCore/Unit_tests/fmri_data/canlab_test_constructor.m new file mode 100644 index 00000000..8d8a519e --- /dev/null +++ b/CanlabCore/Unit_tests/fmri_data/canlab_test_constructor.m @@ -0,0 +1,27 @@ +function tests = canlab_test_constructor +%TEST_CONSTRUCTOR fmri_data constructor invariants. +tests = functiontests(localfunctions); +end + + +function test_empty_constructor_returns_fmri_data(tc) +% fmri_data with no args produces an object of the right class with the +% standard volInfo populated from the default brain mask. +obj = fmri_data(); +tc.verifyClass(obj, 'fmri_data'); +tc.verifyNotEmpty(obj.volInfo, '.volInfo should be populated by the default mask'); +end + + +function test_constructor_from_nifti(tc) +% Construct from a known 4-D NIfTI in Sample_datasets. +img = which('Wager_2008_emo_reg_vs_look_neg_contrast_images.nii.gz'); +tc.assumeNotEmpty(img, 'Sample image not found on path'); + +obj = fmri_data(img, 'noverbose'); +tc.verifyClass(obj, 'fmri_data'); +tc.verifyEqual(size(obj.dat, 2), 30, ... + 'emotionreg sample is expected to contain 30 contrast images'); +tc.verifyGreaterThan(size(obj.dat, 1), 0, ... + '.dat should have at least one in-mask voxel'); +end diff --git a/CanlabCore/Unit_tests/fmri_data/canlab_test_load_sample.m b/CanlabCore/Unit_tests/fmri_data/canlab_test_load_sample.m new file mode 100644 index 00000000..f77e4c08 --- /dev/null +++ b/CanlabCore/Unit_tests/fmri_data/canlab_test_load_sample.m @@ -0,0 +1,18 @@ +function tests = canlab_test_load_sample +%TEST_LOAD_SAMPLE load_image_set keyword resolution for the sample dataset. +tests = functiontests(localfunctions); +end + + +function test_load_image_set_emotionreg(tc) +obj = load_image_set('emotionreg', 'noverbose'); +tc.verifyClass(obj, 'fmri_data'); +tc.verifyEqual(size(obj.dat, 2), 30); +end + + +function test_load_image_set_attaches_paths(tc) +% After load, fullpath / image_names should be populated for provenance. +obj = load_image_set('emotionreg', 'noverbose'); +tc.verifyNotEmpty(obj.fullpath, '.fullpath should record the source image(s)'); +end diff --git a/CanlabCore/Unit_tests/fmri_data/canlab_test_regress.m b/CanlabCore/Unit_tests/fmri_data/canlab_test_regress.m new file mode 100644 index 00000000..5dd5cab0 --- /dev/null +++ b/CanlabCore/Unit_tests/fmri_data/canlab_test_regress.m @@ -0,0 +1,26 @@ +function tests = canlab_test_regress +%CANLAB_TEST_REGRESS Voxelwise regression smoke tests. + +tests = functiontests(localfunctions); +end + + +function test_regress_with_intercept_only(tc) +% With a constant predictor, regress should run and produce stat output. +obj = canlab_get_sample_fmri_data(); +n = size(obj.dat, 2); +obj.X = ones(n, 1); +out = regress(obj, 0.05, 'unc', 'noverbose', 'nodisplay'); +tc.verifyTrue(isstruct(out) || isobject(out)); +end + + +function test_regress_with_one_random_predictor(tc) +% Set a random regressor and verify regress runs end-to-end. +rng(0); +obj = canlab_get_sample_fmri_data(); +n = size(obj.dat, 2); +obj.X = randn(n, 1); +out = regress(obj, 0.05, 'unc', 'noverbose', 'nodisplay'); +tc.verifyTrue(isstruct(out) || isobject(out)); +end diff --git a/CanlabCore/Unit_tests/helpers/canlab_get_sample_fmri_data.m b/CanlabCore/Unit_tests/helpers/canlab_get_sample_fmri_data.m new file mode 100644 index 00000000..0327874e --- /dev/null +++ b/CanlabCore/Unit_tests/helpers/canlab_get_sample_fmri_data.m @@ -0,0 +1,10 @@ +function obj = canlab_get_sample_fmri_data() +%CANLAB_GET_SAMPLE_FMRI_DATA Load the standard emotionreg sample for tests. +% +% obj = canlab_get_sample_fmri_data() returns the 30-image Wager 2008 +% emotion regulation contrast set as an fmri_data object. Used by tests +% to keep the load semantics in one place; if load_image_set's behavior +% shifts, only this helper needs updating. + +obj = load_image_set('emotionreg', 'noverbose'); +end diff --git a/CanlabCore/Unit_tests/helpers/canlab_get_sample_thresholded_t.m b/CanlabCore/Unit_tests/helpers/canlab_get_sample_thresholded_t.m new file mode 100644 index 00000000..2c0da0b7 --- /dev/null +++ b/CanlabCore/Unit_tests/helpers/canlab_get_sample_thresholded_t.m @@ -0,0 +1,17 @@ +function t = canlab_get_sample_thresholded_t(p_thresh) +%CANLAB_GET_SAMPLE_THRESHOLDED_T Build a thresholded statistic_image for tests. +% +% t = canlab_get_sample_thresholded_t() +% t = canlab_get_sample_thresholded_t(p_thresh) % default 0.05 +% +% Loads the emotionreg sample, runs a one-sample voxelwise t-test, and +% thresholds the resulting statistic_image at p < p_thresh (uncorrected). +% Used by display / table / extraction tests as a shared input fixture +% so each one doesn't recompute the t-test independently. + +if nargin < 1, p_thresh = 0.05; end + +obj = canlab_get_sample_fmri_data(); +t = ttest(obj); +t = threshold(t, p_thresh, 'unc'); +end diff --git a/CanlabCore/Unit_tests/image_vector/canlab_test_apply_mask.m b/CanlabCore/Unit_tests/image_vector/canlab_test_apply_mask.m new file mode 100644 index 00000000..e8ac16dd --- /dev/null +++ b/CanlabCore/Unit_tests/image_vector/canlab_test_apply_mask.m @@ -0,0 +1,27 @@ +function tests = canlab_test_apply_mask +%TEST_APPLY_MASK Basic apply_mask behavior. +tests = functiontests(localfunctions); +end + + +function test_apply_mask_preserves_image_count(tc) +% apply_mask changes voxels (rows) but never images (cols). +obj = canlab_get_sample_fmri_data(); +mask_file = which('brainmask_canlab.nii'); +tc.assumeNotEmpty(mask_file, 'brainmask_canlab.nii not on path'); + +obj_masked = apply_mask(obj, mask_file); +tc.verifyClass(obj_masked, 'fmri_data'); +tc.verifyEqual(size(obj_masked.dat, 2), size(obj.dat, 2)); +end + + +function test_apply_mask_returns_same_class(tc) +% apply_mask should preserve object class (fmri_data in -> fmri_data out). +obj = canlab_get_sample_fmri_data(); +mask_file = which('brainmask_canlab.nii'); +tc.assumeNotEmpty(mask_file, 'brainmask_canlab.nii not on path'); + +obj_masked = apply_mask(obj, mask_file); +tc.verifyEqual(class(obj_masked), class(obj)); +end diff --git a/CanlabCore/Unit_tests/image_vector/canlab_test_display.m b/CanlabCore/Unit_tests/image_vector/canlab_test_display.m new file mode 100644 index 00000000..302ca74b --- /dev/null +++ b/CanlabCore/Unit_tests/image_vector/canlab_test_display.m @@ -0,0 +1,70 @@ +function tests = canlab_test_display +%CANLAB_TEST_DISPLAY Display methods on a thresholded t-image (smoke tests). +% +% These exercise the visualization API without verifying pixel content. +% A test passes if the call returns without erroring (and any figures +% created are cleaned up). The GUI-heavy methods (orthviews, surface) +% are guarded so they skip cleanly in headless `matlab -batch` sessions. + +tests = functiontests(localfunctions); +end + + +function setup(tc) %#ok +% Close any figures left around by other tests. +close all force; +end + + +function teardown(tc) %#ok +close all force; +end + + +function test_montage_runs_on_thresholded_t(tc) +t = canlab_get_sample_thresholded_t(); +tc.verifyWarningFree(@() montage(t)); +end + + +function test_slices_runs_on_thresholded_t(tc) +t = canlab_get_sample_thresholded_t(); +% slices() prints output and creates a figure; we only assert no error. +fn = @() slices(t); +tc.verifyWarningFree(@() runAndClose(fn)); +end + + +function test_orthviews_runs_on_thresholded_t(tc) +% orthviews uses SPM's spm_orthviews and requires a Graphics window. +% Skip cleanly when MATLAB has no desktop / display. +tc.assumeTrue(usejava('desktop') || usejava('jvm') && feature('ShowFigureWindows'), ... + 'orthviews requires an interactive figure window'); +t = canlab_get_sample_thresholded_t(); +tc.verifyWarningFree(@() orthviews(t)); +end + + +function test_surface_runs_on_thresholded_t(tc) +% surface() renders meshes; in pure batch this can fail on missing OpenGL. +tc.assumeTrue(usejava('jvm'), 'surface requires Java for rendering'); +t = canlab_get_sample_thresholded_t(); +try + surface(t); +catch ME + if any(strcmp(ME.identifier, ... + {'MATLAB:graphics:opengl:Unavailable', 'MATLAB:graphics:initialize'})) ... + || contains(lower(ME.message), 'opengl') ... + || contains(lower(ME.message), 'display') + tc.assumeFail(['surface() needs a graphics environment: ' ME.message]); + else + rethrow(ME); + end +end +end + + +function runAndClose(fn) +fn(); +close all force; +end diff --git a/CanlabCore/Unit_tests/image_vector/canlab_test_extract_roi.m b/CanlabCore/Unit_tests/image_vector/canlab_test_extract_roi.m new file mode 100644 index 00000000..8624c336 --- /dev/null +++ b/CanlabCore/Unit_tests/image_vector/canlab_test_extract_roi.m @@ -0,0 +1,41 @@ +function tests = canlab_test_extract_roi +%CANLAB_TEST_EXTRACT_ROI Data extraction from regions and masks. + +tests = functiontests(localfunctions); +end + + +function test_extract_roi_averages_with_mask_file(tc) +% Extract one mean per image using the canonical brain mask. This is the +% simplest invocation of extract_roi_averages and exercises the typical +% (data, mask_filename) path that doesn't need on-the-fly resampling. +mask_file = which('brainmask_canlab.nii'); +tc.assumeNotEmpty(mask_file, 'brainmask_canlab.nii not on path'); + +imgs = canlab_get_sample_fmri_data(); +out = extract_roi_averages(imgs, mask_file); +tc.verifyTrue(isstruct(out) || isa(out, 'region')); +tc.verifyGreaterThan(numel(out), 0); + +% from image object +mask = fmri_data(mask_file); +out = extract_roi_averages(imgs, mask_file); + +tc.verifyTrue(isstruct(out) || isa(out, 'region')); +tc.verifyGreaterThan(numel(out), 0); + +end + + +function test_extract_roi_averages_dat_shape_matches_images(tc) +% Each extracted region's .dat should have one row per image (30 for emotionreg). +mask_file = which('brainmask_canlab.nii'); +tc.assumeNotEmpty(mask_file, 'brainmask_canlab.nii not on path'); + +imgs = canlab_get_sample_fmri_data(); +out = extract_roi_averages(imgs, mask_file); +tc.assumeGreaterThan(numel(out), 0, 'no regions to check'); +first = out(1); +tc.verifyTrue(isfield(first, 'dat') || isprop(first, 'dat')); +tc.verifyEqual(size(first.dat, 1), size(imgs.dat, 2)); +end diff --git a/CanlabCore/Unit_tests/image_vector/canlab_test_misc.m b/CanlabCore/Unit_tests/image_vector/canlab_test_misc.m new file mode 100644 index 00000000..94b4db96 --- /dev/null +++ b/CanlabCore/Unit_tests/image_vector/canlab_test_misc.m @@ -0,0 +1,40 @@ +function tests = canlab_test_misc +%CANLAB_TEST_MISC Misc utilities: flip, enforce_variable_types, history. + +tests = functiontests(localfunctions); +end + + +function test_flip_preserves_class_and_image_count(tc) +% L-R flip should not change object class or image count. flip() requires +% a single-image (3-D) object, so reduce emotionreg to its mean first. +obj = mean(canlab_get_sample_fmri_data()); +flipped = flip(obj); +tc.verifyClass(flipped, class(obj)); +tc.verifyEqual(size(flipped.dat, 2), size(obj.dat, 2)); +end + + +function test_flip_twice_round_trips(tc) +% Flipping twice should return an object with the same shape as the input. +obj = mean(canlab_get_sample_fmri_data()); +flipped_twice = flip(flip(obj)); +tc.verifyEqual(size(flipped_twice.dat), size(obj.dat)); +end + + +function test_flip_errors_on_multi_image_object(tc) +% flip() must reject objects with more than one image rather than +% silently producing nonsense via the 3-D slice loop. +obj = canlab_get_sample_fmri_data(); % 30 images +tc.verifyError(@() flip(obj), 'image_vector:flip:multiImage'); +end + + +function test_enforce_variable_types_returns_same_class(tc) +% enforce_variable_types should not change the object's class. +obj = canlab_get_sample_fmri_data(); +casted = enforce_variable_types(obj); +tc.verifyClass(casted, class(obj)); +tc.verifyEqual(size(casted.dat), size(obj.dat)); +end diff --git a/CanlabCore/Unit_tests/image_vector/canlab_test_qc.m b/CanlabCore/Unit_tests/image_vector/canlab_test_qc.m new file mode 100644 index 00000000..b1414a5a --- /dev/null +++ b/CanlabCore/Unit_tests/image_vector/canlab_test_qc.m @@ -0,0 +1,26 @@ +function tests = canlab_test_qc +%CANLAB_TEST_QC Quality-control utilities. + +tests = functiontests(localfunctions); +end + + +function test_descriptives_runs(tc) +obj = canlab_get_sample_fmri_data(); +out = descriptives(obj); +tc.verifyTrue(isstruct(out)); +end + + +function test_qc_metrics_second_level_runs(tc) +% qc_metrics_second_level computes group-level QC stats; smoke test only. +% It can emit benign warnings (missing optional fields), so we don't +% require warning-free — only that the call returns without an error. +obj = canlab_get_sample_fmri_data(); +try + qc_metrics_second_level(obj, 'noplot'); + tc.verifyTrue(true); +catch ME + tc.verifyFail(['qc_metrics_second_level errored: ' ME.message]); +end +end diff --git a/CanlabCore/Unit_tests/image_vector/canlab_test_replace_remove_empty.m b/CanlabCore/Unit_tests/image_vector/canlab_test_replace_remove_empty.m new file mode 100644 index 00000000..cd61517b --- /dev/null +++ b/CanlabCore/Unit_tests/image_vector/canlab_test_replace_remove_empty.m @@ -0,0 +1,45 @@ +function tests = canlab_test_replace_remove_empty +%TEST_REPLACE_REMOVE_EMPTY Round-trip and idempotence of empty-voxel state. +% +% These are the most important invariants in the toolbox: many subtle bugs +% have come from methods that assume the wrong state. We pin down: +% 1. replace_empty -> remove_empty -> replace_empty restores .dat row count +% 2. running the same transform twice is a no-op (idempotence). + +tests = functiontests(localfunctions); +end + + +function test_round_trip_returns_to_padded_size(tc) +obj = canlab_get_sample_fmri_data(); +obj_padded = replace_empty(obj); +n_padded = size(obj_padded.dat, 1); + +obj_compressed = remove_empty(obj_padded); +obj_restored = replace_empty(obj_compressed); + +tc.verifyEqual(size(obj_restored.dat, 1), n_padded, ... + 'replace_empty(remove_empty(replace_empty(obj))) should preserve voxel count'); +end + + +function test_remove_empty_does_not_grow_dat(tc) +obj = canlab_get_sample_fmri_data(); +obj_padded = replace_empty(obj); +obj_compressed = remove_empty(obj_padded); +tc.verifyLessThanOrEqual(size(obj_compressed.dat, 1), size(obj_padded.dat, 1)); +end + + +function test_replace_empty_idempotent(tc) +obj = replace_empty(canlab_get_sample_fmri_data()); +obj2 = replace_empty(obj); +tc.verifyEqual(size(obj.dat), size(obj2.dat)); +end + + +function test_remove_empty_idempotent(tc) +obj = remove_empty(canlab_get_sample_fmri_data()); +obj2 = remove_empty(obj); +tc.verifyEqual(size(obj.dat), size(obj2.dat)); +end diff --git a/CanlabCore/Unit_tests/image_vector/canlab_test_resample_space.m b/CanlabCore/Unit_tests/image_vector/canlab_test_resample_space.m new file mode 100644 index 00000000..d342b95c --- /dev/null +++ b/CanlabCore/Unit_tests/image_vector/canlab_test_resample_space.m @@ -0,0 +1,25 @@ +function tests = canlab_test_resample_space +%CANLAB_TEST_RESAMPLE_SPACE Resampling and space-comparison utilities. + +tests = functiontests(localfunctions); +end + + +function test_compare_space_self_is_zero(tc) +% Comparing an object to itself should report identical space. +% compare_space documents return 0 for same-space; in practice it returns +% the result of any(...), which is logical false. We accept either. +obj = canlab_get_sample_fmri_data(); +result = compare_space(obj, obj); +tc.verifyFalse(logical(result), 'compare_space(obj, obj) should indicate same space'); +end + + +function test_resample_space_to_self_preserves_image_count(tc) +% Resampling onto the same space should be a no-op for image count. +obj = canlab_get_sample_fmri_data(); +n_imgs = size(obj.dat, 2); +resampled = resample_space(obj, obj); +tc.verifyEqual(size(resampled.dat, 2), n_imgs); +tc.verifyClass(resampled, class(obj)); +end diff --git a/CanlabCore/Unit_tests/image_vector/canlab_test_similarity.m b/CanlabCore/Unit_tests/image_vector/canlab_test_similarity.m new file mode 100644 index 00000000..847e7d97 --- /dev/null +++ b/CanlabCore/Unit_tests/image_vector/canlab_test_similarity.m @@ -0,0 +1,29 @@ +function tests = canlab_test_similarity +%CANLAB_TEST_SIMILARITY Spatial-similarity / annotation methods. + +tests = functiontests(localfunctions); +end + + +function test_jackknife_similarity_runs(tc) +% jackknife_similarity computes leave-one-out similarity across images. +% Uses canonical 'doplot'/'verbose' flags (logical) per the function's +% inputParser, not a bare 'noplot' string. +obj = canlab_get_sample_fmri_data(); +result = jackknife_similarity(obj, 'doplot', false, 'verbose', false); +tc.verifyTrue(isnumeric(result) || isstruct(result)); +end + + +function test_image_similarity_plot_runs(tc) +% image_similarity_plot needs a basis set; the bucknermaps variant pulls +% one in by name. Skip cleanly if the maps repo isn't on path. +tc.assumeNotEmpty(which('Buckner_Yeo_networks_7.nii.gz') | ... + which('Yeo_7networks.nii') | ... + which('rBucknerlab_wholebrain.nii'), ... + 'Buckner network maps not on path'); +obj = canlab_get_sample_fmri_data(); +m = mean(obj); +fn = @() image_similarity_plot_bucknermaps(m, 'noplot'); +tc.verifyWarningFree(fn); +end diff --git a/CanlabCore/Unit_tests/check_roi_extraction.m b/CanlabCore/Unit_tests/old_to_integrate/check_roi_extraction.m similarity index 100% rename from CanlabCore/Unit_tests/check_roi_extraction.m rename to CanlabCore/Unit_tests/old_to_integrate/check_roi_extraction.m diff --git a/CanlabCore/Unit_tests/jackknife_similarity_unit_test.m b/CanlabCore/Unit_tests/old_to_integrate/jackknife_similarity_unit_test.m similarity index 100% rename from CanlabCore/Unit_tests/jackknife_similarity_unit_test.m rename to CanlabCore/Unit_tests/old_to_integrate/jackknife_similarity_unit_test.m diff --git a/CanlabCore/Unit_tests/resampling_pattern_expression_unit_test1.m b/CanlabCore/Unit_tests/old_to_integrate/resampling_pattern_expression_unit_test1.m similarity index 100% rename from CanlabCore/Unit_tests/resampling_pattern_expression_unit_test1.m rename to CanlabCore/Unit_tests/old_to_integrate/resampling_pattern_expression_unit_test1.m diff --git a/CanlabCore/Unit_tests/region/canlab_test_region_from_stat.m b/CanlabCore/Unit_tests/region/canlab_test_region_from_stat.m new file mode 100644 index 00000000..66f3dbb9 --- /dev/null +++ b/CanlabCore/Unit_tests/region/canlab_test_region_from_stat.m @@ -0,0 +1,23 @@ +function tests = canlab_test_region_from_stat +%TEST_REGION_FROM_STAT Building a region object from a thresholded map. +tests = functiontests(localfunctions); +end + + +function test_region_returns_region_object(tc) +% Use a permissive threshold so we get at least one cluster on the sample. +obj = canlab_get_sample_fmri_data(); +t = ttest(obj); +t = threshold(t, 0.05, 'unc'); +r = region(t); +tc.verifyClass(r, 'region'); +end + + +function test_region_count_nonnegative(tc) +obj = canlab_get_sample_fmri_data(); +t = ttest(obj); +t = threshold(t, 0.05, 'unc'); +r = region(t); +tc.verifyGreaterThanOrEqual(numel(r), 0); +end diff --git a/CanlabCore/Unit_tests/statistic_image/canlab_test_table.m b/CanlabCore/Unit_tests/statistic_image/canlab_test_table.m new file mode 100644 index 00000000..bf607038 --- /dev/null +++ b/CanlabCore/Unit_tests/statistic_image/canlab_test_table.m @@ -0,0 +1,27 @@ +function tests = canlab_test_table +%CANLAB_TEST_TABLE Building tables from a thresholded statistic_image. + +tests = functiontests(localfunctions); +end + + +function test_table_runs_on_thresholded_t(tc) +% table() prints to the command window and may return a region array. +% We only check that it runs without erroring on a permissive threshold. +t = canlab_get_sample_thresholded_t(); +tc.verifyWarningFree(@() table(t, 'nolegend')); +end + + +function test_table_returns_something_useful(tc) +% Capture an output if table() returns one. +t = canlab_get_sample_thresholded_t(); +try + r = table(t, 'nolegend'); + if ~isempty(r) + tc.verifyTrue(isa(r, 'region') || isstruct(r) || istable(r)); + end +catch ME + tc.verifyFail(['table() should not error: ' ME.message]); +end +end diff --git a/CanlabCore/Unit_tests/statistic_image/canlab_test_ttest.m b/CanlabCore/Unit_tests/statistic_image/canlab_test_ttest.m new file mode 100644 index 00000000..e0b0dbbd --- /dev/null +++ b/CanlabCore/Unit_tests/statistic_image/canlab_test_ttest.m @@ -0,0 +1,32 @@ +function tests = canlab_test_ttest +%TEST_TTEST Voxelwise one-sample t-test on an fmri_data object. +tests = functiontests(localfunctions); +end + + +function test_ttest_returns_statistic_image(tc) +obj = canlab_get_sample_fmri_data(); +t = ttest(obj); +tc.verifyClass(t, 'statistic_image'); +tc.verifyGreaterThan(size(t.dat, 1), 0); +end + + +function test_threshold_returns_statistic_image(tc) +obj = canlab_get_sample_fmri_data(); +t = ttest(obj); +t_thr = threshold(t, 0.005, 'unc'); +tc.verifyClass(t_thr, 'statistic_image'); +end + + +function test_threshold_marks_significant_voxels(tc) +% After thresholding, some voxels should be flagged in .sig (or none, if +% the threshold is too strict). We just verify the field exists and is +% the right shape. +obj = canlab_get_sample_fmri_data(); +t = ttest(obj); +t_thr = threshold(t, 0.05, 'unc'); +tc.verifyTrue(isprop(t_thr, 'sig')); +tc.verifyEqual(size(t_thr.sig, 1), size(t_thr.dat, 1)); +end diff --git a/CanlabCore/Unit_tests/walkthroughs/canlab_test_walkthrough_1_installing_tools.m b/CanlabCore/Unit_tests/walkthroughs/canlab_test_walkthrough_1_installing_tools.m new file mode 100644 index 00000000..5398e0e4 --- /dev/null +++ b/CanlabCore/Unit_tests/walkthroughs/canlab_test_walkthrough_1_installing_tools.m @@ -0,0 +1,34 @@ +function tests = canlab_test_walkthrough_1_installing_tools +%CANLAB_TEST_WALKTHROUGH_1_INSTALLING_TOOLS End-to-end smoke test of canlab_help_1_installing_tools. +% +% Wrapper around CANlab_help_examples/example_help_files/canlab_help_1_installing_tools.m. +% Acts as a Tier B integration test: confirms the walkthrough runs end-to-end +% on the current toolbox without erroring. Does not check pixel content, +% statistics, or output values; reaching the end of the script counts as a pass. +% +% Skipped (Incomplete) if CANlab_help_examples is not on the MATLAB path. + +tests = functiontests(localfunctions); +end + + +function setup(tc) %#ok<*DEFNU> +% Run each walkthrough in its own temp working directory so output files +% (NIfTI writes, saved figures) don't collide with prior runs. +tc.applyFixture(matlab.unittest.fixtures.WorkingFolderFixture); +close all force +end + + +function teardown(tc) +close all force +end + + +function test_runs_to_completion(tc) +script_name = 'canlab_help_1_installing_tools'; +tc.assumeNotEmpty(which(script_name), ... + 'CANlab_help_examples (example_help_files/) must be on the MATLAB path'); +evalc(script_name); +tc.verifyTrue(true); +end diff --git a/CanlabCore/Unit_tests/walkthroughs/canlab_test_walkthrough_2_load_a_sample_dataset.m b/CanlabCore/Unit_tests/walkthroughs/canlab_test_walkthrough_2_load_a_sample_dataset.m new file mode 100644 index 00000000..0ea0f7cd --- /dev/null +++ b/CanlabCore/Unit_tests/walkthroughs/canlab_test_walkthrough_2_load_a_sample_dataset.m @@ -0,0 +1,29 @@ +function tests = canlab_test_walkthrough_2_load_a_sample_dataset +%CANLAB_TEST_WALKTHROUGH_2_LOAD_A_SAMPLE_DATASET Smoke test of canlab_help_2_load_a_sample_dataset. +% +% Tier B integration test: runs the walkthrough end-to-end and counts a +% return without error as a pass. See canlab_test_walkthrough_1_installing_tools +% for shared rationale. + +tests = functiontests(localfunctions); +end + + +function setup(tc) %#ok<*DEFNU> +tc.applyFixture(matlab.unittest.fixtures.WorkingFolderFixture); +close all force +end + + +function teardown(tc) +close all force +end + + +function test_runs_to_completion(tc) +script_name = 'canlab_help_2_load_a_sample_dataset'; +tc.assumeNotEmpty(which(script_name), ... + 'CANlab_help_examples (example_help_files/) must be on the MATLAB path'); +evalc(script_name); +tc.verifyTrue(true); +end diff --git a/CanlabCore/Unit_tests/walkthroughs/canlab_test_walkthrough_2b_basic_image_visualization.m b/CanlabCore/Unit_tests/walkthroughs/canlab_test_walkthrough_2b_basic_image_visualization.m new file mode 100644 index 00000000..a836e12a --- /dev/null +++ b/CanlabCore/Unit_tests/walkthroughs/canlab_test_walkthrough_2b_basic_image_visualization.m @@ -0,0 +1,28 @@ +function tests = canlab_test_walkthrough_2b_basic_image_visualization +%CANLAB_TEST_WALKTHROUGH_2B_BASIC_IMAGE_VISUALIZATION Smoke test of canlab_help_2b_basic_image_visualization. +% +% Tier B integration test. See canlab_test_walkthrough_1_installing_tools +% for shared rationale and conventions. + +tests = functiontests(localfunctions); +end + + +function setup(tc) %#ok<*DEFNU> +tc.applyFixture(matlab.unittest.fixtures.WorkingFolderFixture); +close all force +end + + +function teardown(tc) +close all force +end + + +function test_runs_to_completion(tc) +script_name = 'canlab_help_2b_basic_image_visualization'; +tc.assumeNotEmpty(which(script_name), ... + 'CANlab_help_examples (example_help_files/) must be on the MATLAB path'); +evalc(script_name); +tc.verifyTrue(true); +end diff --git a/CanlabCore/Unit_tests/walkthroughs/canlab_test_walkthrough_2c_loading_datasets.m b/CanlabCore/Unit_tests/walkthroughs/canlab_test_walkthrough_2c_loading_datasets.m new file mode 100644 index 00000000..f1d3a208 --- /dev/null +++ b/CanlabCore/Unit_tests/walkthroughs/canlab_test_walkthrough_2c_loading_datasets.m @@ -0,0 +1,28 @@ +function tests = canlab_test_walkthrough_2c_loading_datasets +%CANLAB_TEST_WALKTHROUGH_2C_LOADING_DATASETS Smoke test of canlab_help_2c_loading_datasets. +% +% Tier B integration test. See canlab_test_walkthrough_1_installing_tools +% for shared rationale and conventions. + +tests = functiontests(localfunctions); +end + + +function setup(tc) %#ok<*DEFNU> +tc.applyFixture(matlab.unittest.fixtures.WorkingFolderFixture); +close all force +end + + +function teardown(tc) +close all force +end + + +function test_runs_to_completion(tc) +script_name = 'canlab_help_2c_loading_datasets'; +tc.assumeNotEmpty(which(script_name), ... + 'CANlab_help_examples (example_help_files/) must be on the MATLAB path'); +evalc(script_name); +tc.verifyTrue(true); +end diff --git a/CanlabCore/Unit_tests/walkthroughs/canlab_test_walkthrough_3_voxelwise_t_test.m b/CanlabCore/Unit_tests/walkthroughs/canlab_test_walkthrough_3_voxelwise_t_test.m new file mode 100644 index 00000000..dc36fced --- /dev/null +++ b/CanlabCore/Unit_tests/walkthroughs/canlab_test_walkthrough_3_voxelwise_t_test.m @@ -0,0 +1,30 @@ +function tests = canlab_test_walkthrough_3_voxelwise_t_test +%CANLAB_TEST_WALKTHROUGH_3_VOXELWISE_T_TEST Smoke test of canlab_help_3_voxelwise_t_test_walkthrough. +% +% Tier B integration test. See canlab_test_walkthrough_1_installing_tools +% for shared rationale and conventions. The WorkingFolderFixture in setup +% provides a clean cwd, which matters here because the walkthrough writes +% NIfTI output files. + +tests = functiontests(localfunctions); +end + + +function setup(tc) %#ok<*DEFNU> +tc.applyFixture(matlab.unittest.fixtures.WorkingFolderFixture); +close all force +end + + +function teardown(tc) +close all force +end + + +function test_runs_to_completion(tc) +script_name = 'canlab_help_3_voxelwise_t_test_walkthrough'; +tc.assumeNotEmpty(which(script_name), ... + 'CANlab_help_examples (example_help_files/) must be on the MATLAB path'); +evalc(script_name); +tc.verifyTrue(true); +end diff --git a/CanlabCore/Unit_tests/walkthroughs/canlab_test_walkthrough_3b_atlases_and_ROI.m b/CanlabCore/Unit_tests/walkthroughs/canlab_test_walkthrough_3b_atlases_and_ROI.m new file mode 100644 index 00000000..bbf8135c --- /dev/null +++ b/CanlabCore/Unit_tests/walkthroughs/canlab_test_walkthrough_3b_atlases_and_ROI.m @@ -0,0 +1,28 @@ +function tests = canlab_test_walkthrough_3b_atlases_and_ROI +%CANLAB_TEST_WALKTHROUGH_3B_ATLASES_AND_ROI Smoke test of canlab_help_3b_atlases_and_ROI_analysis. +% +% Tier B integration test. See canlab_test_walkthrough_1_installing_tools +% for shared rationale and conventions. + +tests = functiontests(localfunctions); +end + + +function setup(tc) %#ok<*DEFNU> +tc.applyFixture(matlab.unittest.fixtures.WorkingFolderFixture); +close all force +end + + +function teardown(tc) +close all force +end + + +function test_runs_to_completion(tc) +script_name = 'canlab_help_3b_atlases_and_ROI_analysis'; +tc.assumeNotEmpty(which(script_name), ... + 'CANlab_help_examples (example_help_files/) must be on the MATLAB path'); +evalc(script_name); +tc.verifyTrue(true); +end diff --git a/CanlabCore/Unit_tests/walkthroughs/canlab_test_walkthrough_4_masking_and_writing.m b/CanlabCore/Unit_tests/walkthroughs/canlab_test_walkthrough_4_masking_and_writing.m new file mode 100644 index 00000000..7958e2eb --- /dev/null +++ b/CanlabCore/Unit_tests/walkthroughs/canlab_test_walkthrough_4_masking_and_writing.m @@ -0,0 +1,30 @@ +function tests = canlab_test_walkthrough_4_masking_and_writing +%CANLAB_TEST_WALKTHROUGH_4_MASKING_AND_WRITING Smoke test of canlab_help_4_masking_and_writing_nifti_files. +% +% Tier B integration test. See canlab_test_walkthrough_1_installing_tools +% for shared rationale and conventions. The WorkingFolderFixture in setup +% provides a clean cwd, which matters here because the walkthrough writes +% NIfTI output files. + +tests = functiontests(localfunctions); +end + + +function setup(tc) %#ok<*DEFNU> +tc.applyFixture(matlab.unittest.fixtures.WorkingFolderFixture); +close all force +end + + +function teardown(tc) +close all force +end + + +function test_runs_to_completion(tc) +script_name = 'canlab_help_4_masking_and_writing_nifti_files'; +tc.assumeNotEmpty(which(script_name), ... + 'CANlab_help_examples (example_help_files/) must be on the MATLAB path'); +evalc(script_name); +tc.verifyTrue(true); +end diff --git a/CanlabCore/Unit_tests/walkthroughs/canlab_test_walkthrough_4b_3D_visualization.m b/CanlabCore/Unit_tests/walkthroughs/canlab_test_walkthrough_4b_3D_visualization.m new file mode 100644 index 00000000..cc52a045 --- /dev/null +++ b/CanlabCore/Unit_tests/walkthroughs/canlab_test_walkthrough_4b_3D_visualization.m @@ -0,0 +1,28 @@ +function tests = canlab_test_walkthrough_4b_3D_visualization +%CANLAB_TEST_WALKTHROUGH_4B_3D_VISUALIZATION Smoke test of canlab_help_4b_3D_visualization. +% +% Tier B integration test. See canlab_test_walkthrough_1_installing_tools +% for shared rationale and conventions. + +tests = functiontests(localfunctions); +end + + +function setup(tc) %#ok<*DEFNU> +tc.applyFixture(matlab.unittest.fixtures.WorkingFolderFixture); +close all force +end + + +function teardown(tc) +close all force +end + + +function test_runs_to_completion(tc) +script_name = 'canlab_help_4b_3D_visualization'; +tc.assumeNotEmpty(which(script_name), ... + 'CANlab_help_examples (example_help_files/) must be on the MATLAB path'); +evalc(script_name); +tc.verifyTrue(true); +end diff --git a/CanlabCore/Unit_tests/walkthroughs/canlab_test_walkthrough_5_regression.m b/CanlabCore/Unit_tests/walkthroughs/canlab_test_walkthrough_5_regression.m new file mode 100644 index 00000000..b0d2ca7b --- /dev/null +++ b/CanlabCore/Unit_tests/walkthroughs/canlab_test_walkthrough_5_regression.m @@ -0,0 +1,28 @@ +function tests = canlab_test_walkthrough_5_regression +%CANLAB_TEST_WALKTHROUGH_5_REGRESSION Smoke test of canlab_help_5_regression_walkthrough. +% +% Tier B integration test. See canlab_test_walkthrough_1_installing_tools +% for shared rationale and conventions. + +tests = functiontests(localfunctions); +end + + +function setup(tc) %#ok<*DEFNU> +tc.applyFixture(matlab.unittest.fixtures.WorkingFolderFixture); +close all force +end + + +function teardown(tc) +close all force +end + + +function test_runs_to_completion(tc) +script_name = 'canlab_help_5_regression_walkthrough'; +tc.assumeNotEmpty(which(script_name), ... + 'CANlab_help_examples (example_help_files/) must be on the MATLAB path'); +evalc(script_name); +tc.verifyTrue(true); +end diff --git a/CanlabCore/Unit_tests/walkthroughs/canlab_test_walkthrough_7_multivariate_prediction.m b/CanlabCore/Unit_tests/walkthroughs/canlab_test_walkthrough_7_multivariate_prediction.m new file mode 100644 index 00000000..f8d86804 --- /dev/null +++ b/CanlabCore/Unit_tests/walkthroughs/canlab_test_walkthrough_7_multivariate_prediction.m @@ -0,0 +1,28 @@ +function tests = canlab_test_walkthrough_7_multivariate_prediction +%CANLAB_TEST_WALKTHROUGH_7_MULTIVARIATE_PREDICTION Smoke test of canlab_help_7_multivariate_prediction_basics. +% +% Tier B integration test. See canlab_test_walkthrough_1_installing_tools +% for shared rationale and conventions. + +tests = functiontests(localfunctions); +end + + +function setup(tc) %#ok<*DEFNU> +tc.applyFixture(matlab.unittest.fixtures.WorkingFolderFixture); +close all force +end + + +function teardown(tc) +close all force +end + + +function test_runs_to_completion(tc) +script_name = 'canlab_help_7_multivariate_prediction_basics'; +tc.assumeNotEmpty(which(script_name), ... + 'CANlab_help_examples (example_help_files/) must be on the MATLAB path'); +evalc(script_name); +tc.verifyTrue(true); +end diff --git a/CanlabCore/Unit_tests/workflows/canlab_test_group_ttest_workflow.m b/CanlabCore/Unit_tests/workflows/canlab_test_group_ttest_workflow.m new file mode 100644 index 00000000..5a9e907c --- /dev/null +++ b/CanlabCore/Unit_tests/workflows/canlab_test_group_ttest_workflow.m @@ -0,0 +1,26 @@ +function tests = canlab_test_group_ttest_workflow +%TEST_GROUP_TTEST_WORKFLOW End-to-end smoke test of the canonical group-level pipeline. +% +% Validates that the chain +% load_image_set -> ttest -> threshold -> region +% runs without error on the sample dataset and produces objects of the +% expected classes. This is an integration check, not a numerical-correctness +% check; per-step numerical invariants belong in the per-class test files. + +tests = functiontests(localfunctions); +end + + +function test_full_workflow(tc) +imgs = load_image_set('emotionreg', 'noverbose'); +tc.verifyClass(imgs, 'fmri_data'); + +t = ttest(imgs); +tc.verifyClass(t, 'statistic_image'); + +t = threshold(t, 0.005, 'unc'); +tc.verifyClass(t, 'statistic_image'); + +r = region(t); +tc.verifyClass(r, 'region'); +end diff --git a/CanlabCore/Visualization_functions/canlab_canonical_brain_surface_cutaways.m b/CanlabCore/Visualization_functions/canlab_canonical_brain_surface_cutaways.m index d110ce94..d792e55e 100644 --- a/CanlabCore/Visualization_functions/canlab_canonical_brain_surface_cutaways.m +++ b/CanlabCore/Visualization_functions/canlab_canonical_brain_surface_cutaways.m @@ -1,54 +1,34 @@ function [surface_handles, ax] = canlab_canonical_brain_surface_cutaways(method_keyword, varargin) -% Create one of a number of pre-set 3D brain views, and return surface handles for rendering +% canlab_canonical_brain_surface_cutaways Create a pre-set 3D canonical brain cutaway view and return surface handles. % % :Usage: % :: % -% [surface_handles, axis_handles] = canlab_canonical_brain_surface_cutaways(method_keyword, ['noverbose']) +% [surface_handles, ax] = canlab_canonical_brain_surface_cutaways(method_keyword, ['noverbose']) % -% - Uses fmri_data.isosurface method, which is a flexible way of creating surfaces -% - You can then render activation blobs on these surfaces using the -% surface() object methods for CANlab objects or cluster_surf +% Creates one of a number of pre-set 3D brain views and returns surface +% handles for rendering. The function: % -% - Uses a pre-set group-average anatomical image tuned for quality visual display -% ('keuken_2014_enhanced_for_underlay.img') from Keuken et al. 7T atlas +% - Uses the fmri_data.isosurface method, a flexible way of creating +% surfaces. You can then render activation blobs on these surfaces +% using the surface() object methods for CANlab objects or +% cluster_surf. +% - Uses a pre-set group-average anatomical image tuned for quality +% visual display ('keuken_2014_enhanced_for_underlay.img') from the +% Keuken et al. 7T atlas. +% - Is called from addbrain, which has many options for surface +% rendering. +% - fmri_data.isosurface() can be used to create many more custom +% surfaces. See this code for examples. % -% Keuken et al. (2014). Quantifying inter-individual anatomical variability in the subcortex using 7T structural MRI -% Forstmann et al. (2014). Forstmann, Birte U., Max C. Keuken, Andreas Schafer, Pierre-Louis Bazin, Anneke Alkemade, and Robert Turner. 2014. ?Multi-Modal Ultra-High Resolution Structural 7-Tesla MRI Data Repository.? Scientific Data 1 (December): 140050. +% :References: +% - Keuken et al. (2014). Quantifying inter-individual anatomical +% variability in the subcortex using 7T structural MRI. +% - Forstmann, Birte U., Max C. Keuken, Andreas Schafer, Pierre-Louis +% Bazin, Anneke Alkemade, and Robert Turner. 2014. "Multi-Modal +% Ultra-High Resolution Structural 7-Tesla MRI Data Repository." +% Scientific Data 1 (December): 140050. % -% - This function is called from addbrain, which has many options for -% surface rendering. -% -% - fmri_data.isosurface() can be used to create many more custom surfaces -% See this code for examples. -% -% :Inputs: -% -% **method_keyword:** One of the following: -% 'left_cutaway' -% 'right_cutaway' -% 'right_cutaway_x8' % like right_cutaway but x=8 -% 'left_insula_slab' -% 'right_insula_slab' -% 'accumbens_slab' -% -% :Outputs: -% **A figure display with rendering** -% -% **surface_handles:** -% A vector of handles to surface objects (isosurfaces and isocaps) -% -% % To recreate these isosurfaces, do this: -% anat = fmri_data(which('keuken_2014_enhanced_for_underlay.img'), 'noverbose'); -% [isosurf, isocap] = deal({}); -% [p, ~, isosurf{1}, isocap{1}] = isosurface(anat, 'thresh', 140, 'nosmooth', 'ylim', [-Inf -30]); -% [p2, ~, isosurf{2}, isocap{2}] = isosurface(anat, 'thresh', 140, 'nosmooth', 'xlim', [-Inf 0], 'YLim', [-30 Inf]); -% -% :See also: -% fmri_data.isosurface, addbrain, cluster_cutaways, fmri_data.surface, -% region.surface -% - % .. % Author and copyright information: % @@ -68,6 +48,50 @@ % along with this program. If not, see . % .. % +% :Inputs: +% +% **method_keyword:** +% One of the following keywords: +% +% - 'left_cutaway' +% - 'right_cutaway' +% - 'right_cutaway_x8' (like right_cutaway but x=8) +% - 'left_insula_slab' +% - 'right_insula_slab' +% - 'accumbens_slab' +% +% :Optional Inputs: +% +% **'noverbose':** +% Suppress verbose output. Default is verbose on. +% +% :Outputs: +% +% **surface_handles:** +% A vector of handles to surface objects (isosurfaces and +% isocaps). A figure with the rendered surfaces is also produced. +% +% **ax:** +% Handle to the current axes containing the rendered surfaces. +% +% :Examples: +% :: +% +% % To recreate these isosurfaces from the canonical anatomy: +% anat = fmri_data(which('keuken_2014_enhanced_for_underlay.img'), 'noverbose'); +% [isosurf, isocap] = deal({}); +% [p, ~, isosurf{1}, isocap{1}] = isosurface(anat, 'thresh', 140, ... +% 'nosmooth', 'ylim', [-Inf -30]); +% [p2, ~, isosurf{2}, isocap{2}] = isosurface(anat, 'thresh', 140, ... +% 'nosmooth', 'xlim', [-Inf 0], 'YLim', [-30 Inf]); +% +% :See also: +% - fmri_data.isosurface +% - addbrain +% - cluster_cutaways +% - fmri_data.surface +% - region.surface +% % load this to create new isosurfaces, but not needed to load existing saved ones: % anat = fmri_data(which('keuken_2014_enhanced_for_underlay.img'), 'noverbose'); diff --git a/CanlabCore/Visualization_functions/canlab_results_fmridisplay.m b/CanlabCore/Visualization_functions/canlab_results_fmridisplay.m index 86390486..58d44548 100644 --- a/CanlabCore/Visualization_functions/canlab_results_fmridisplay.m +++ b/CanlabCore/Visualization_functions/canlab_results_fmridisplay.m @@ -1,187 +1,223 @@ function o2 = canlab_results_fmridisplay(input_activation, varargin) +% canlab_results_fmridisplay Display fMRI results blobs on canonical anatomical slices and surfaces. +% % :Usage: % :: % -% canlab_results_fmridisplay(input_activation, [optional inputs]) +% o2 = canlab_results_fmridisplay(input_activation, [optional inputs]) % -% purpose: This function display fmri results. +% Sets up anatomical underlays (axial / coronal / sagittal slices and/or +% cortical surfaces) and overlays activation blobs from an image file, +% region object, or fmri_data / statistic_image object. Returns an +% fmridisplay object that can be reused with addblobs, removeblobs, and +% render_on_surface methods. You can also pass an existing fmridisplay +% object as one of the inputs and it will be used instead of setting up +% canonical slices. % -% :Input: +% Tip: Try brighten(.4) to make the images brighter. % -% **input_activation:** -% nii, img, +% .. +% Tor Wager +% 1/27/2012 +% .. % -% This image has the blobs you want to -% display. You can also enter a cl "clusters" structure or -% "region" object. +% :Inputs: % -% you can also get a thresholded image like the examples used here -% from a number of places - by thresholding your results in SPM -% and using "write filtered" to save the image, by creating masks -% from meta-analysis or anatomical atlases, or by using +% **input_activation:** +% Image (nii, img) to display, a cl 'clusters' structure, a +% 'region' object, or an fmri_data / statistic_image object whose +% blobs you want to display. +% +% You can also obtain a thresholded image suitable for input from +% a number of places: by thresholding your results in SPM and +% using 'write filtered' to save the image; by creating masks +% from meta-analysis or anatomical atlases; or by using % mediation_brain_results, robust_results_threshold, -% robust_results_batch_script, threshold_imgs, or object -% oriented tools including fmri_data and statistic_image objects. +% robust_results_batch_script, threshold_imgs, or object oriented +% tools including fmri_data and statistic_image objects. % % :Optional Inputs: % % **'noblobs':** -% do not display blobs +% Do not display blobs. % % **'outline':** -% display blob outlines +% Display blob outlines. % % **'nooutline':** -% do not display blob outlines (default) +% Do not display blob outlines (default). % % **'addmontages':** -% when entering existing fmridisplay obj, add new montages +% When entering an existing fmridisplay obj, add new montages. % % **'noremove':** -% do not remove current blobs when adding new ones +% Do not remove current blobs when adding new ones. % % **'nofigure':** -% do not create a new figure (for selected montage sets only) +% Do not create a new figure (for selected montage sets only). % -% **'outlinecolor:** -% followed by new outline color +% **'outlinecolor':** +% Followed by new outline color. % % **'splitcolor':** -% followed by 4-cell new split colormap colors (help fmridisplay or edit code for defaults as example) +% Followed by a 4-cell new split colormap colors specification +% (see help fmridisplay or the function code for defaults). % % **'montagetype':** -% Note: for surface plotting MNI surface projection is available. See help render_on_surface for additional -% options to specify to enable the necessary transformations. Otherwise naive sampling based on naive surface -% vertex coordinates will be used, which in most cases will not correctly map to your data volume. -% -% 'full' Axial, coronal, and saggital slices, 4 cortical surfaces -% 'compact' Midline saggital and two rows of axial slices [the default] -% 'compact2' A single row showing midline saggital and axial slices -% 'compact3' One row of axial slices, midline sagg, and 4 HCP surfaces -% 'multirow' A series of 'compact2' displays in one figure for comparing different images/maps side by side -% 'regioncenters' A series of separate axes, each focused on one region -% 'full2' for a slightly less full montage that avoids colorbar overlap issues -% 'full hcp' for full montage, but with surfaces and volumes from HCP data -% 'full hcp inflated' for full montage using hcp inflated surfaces -% 'hcp grayordinates' for 4 surfaces and 18 zoomed in subcortical slices -% 'hcp grayordinates compact' for 4 surfaces and 4 zoomed in subcortical slices -% 'hcp grayordinates subcortex' -% for zoomed in subcortical slices -% 'hcp inflated' for a connectome workbench style layout without -% volumetric slices -% 'freesurfer inflated' connectome workbench style layout (no volumetric slices) with fsaverage 164k surfaces. -% 'MNI152NLin[2009c|6]Asym [pial|midthickness|white] -% Connectome workbench style layout (no volumetric slices) using MNI152NLin2009cAsym or MNI152NLin6Asym -% surfaces sampled at one of three depths. This will work with data that's already in the corresponding -% MNI template space with naive mesh interpolation (no need for special surface projection -% transformations), unlike all other surfaces (see help render_on_surface for details on projection -% options otherwise). When projecting data to other surfaces you need a sampling depth though and these -% MNI space surfaces can be helpful for deciding on a sampling depth to use in your projections (see -% srcdepth argument to render_on_surface). Simply render on the surface corresponding to your data's -% template space and the desired depth to see what data would be extracted from your volumes with that -% srcdepth argument (pial, midthickness, white), and then include that with your srcdepth argument when -% plotting your surface data. +% Note: for surface plotting, MNI surface projection is available. +% See help render_on_surface for additional options to specify +% the necessary transformations. Otherwise naive sampling based +% on surface vertex coordinates will be used, which in most cases +% will not correctly map to your data volume. % -% 'compact' [default] for single-figure parasagittal and axials slices. +% Available types: % -% 'compact2': like 'compact', but fewer axial slices. +% - 'full' Axial, coronal, and sagittal slices, 4 +% cortical surfaces. +% - 'compact' Midline sagittal and two rows of axial +% slices [the default]. +% - 'compact2' A single row showing midline sagittal and +% axial slices. +% - 'compact3' One row of axial slices, midline sagittal, +% and 4 HCP surfaces. +% - 'multirow' A series of 'compact2' displays in one +% figure for comparing different images/maps side by side. +% Followed by number of rows, e.g., +% o2 = canlab_results_fmridisplay([], 'multirow', 2); +% - 'regioncenters' A series of separate axes, each focused on +% one region. +% - 'full2' For a slightly less full montage that +% avoids colorbar overlap issues. +% - 'full hcp' Full montage, but with surfaces and volumes +% from HCP data. +% - 'full hcp inflated' Full montage using HCP inflated surfaces. +% - 'hcp grayordinates' 4 surfaces and 18 zoomed-in subcortical +% slices. +% - 'hcp grayordinates compact' 4 surfaces and 4 zoomed-in +% subcortical slices. +% - 'hcp grayordinates subcortex' Zoomed-in subcortical slices. +% - 'hcp inflated' Connectome Workbench-style layout without +% volumetric slices. +% - 'freesurfer inflated' Connectome Workbench-style layout (no +% volumetric slices) with fsaverage 164k surfaces. +% - 'MNI152NLin[2009c|6]Asym [pial|midthickness|white]' +% Connectome Workbench-style layout (no volumetric slices) +% using MNI152NLin2009cAsym or MNI152NLin6Asym surfaces sampled +% at one of three depths. This will work with data that's +% already in the corresponding MNI template space with naive +% mesh interpolation (no need for special surface projection +% transformations), unlike all other surfaces (see help +% render_on_surface for details on projection options +% otherwise). When projecting data to other surfaces you need a +% sampling depth, and these MNI space surfaces can be helpful +% for deciding on a sampling depth to use in your projections +% (see srcdepth argument to render_on_surface). Simply render +% on the surface corresponding to your data's template space +% and the desired depth to see what data would be extracted +% from your volumes with that srcdepth argument (pial, +% midthickness, white), and then include that with your +% srcdepth argument when plotting your surface data. +% - {'blobcenters', 'regioncenters'}: Slices for the center of +% each blob/region. Note: this creates a new figure, tagged +% 'fmridisplay_regioncenters', and is not compatible with +% 'nofigure'. % -% 'multirow': followed by number of rows -% e.g., o2 = canlab_results_fmridisplay([], 'multirow', 2); -% -% {'blobcenters', 'regioncenters'}: Slices for the center of each blob/region -% Note: this creates a new figure, tagged -% 'fmridisplay_regioncenters', and is not compatible with 'nofigure' -% % **'noverbose':** -% suppress verbose output, good for scripts/publish to html, etc. +% Suppress verbose output, good for scripts/publish to html, etc. % % **'overlay':** -% specify anatomical image for montage (not surfaces), followed by -% image name -% e.g., o2 = canlab_results_fmridisplay([], 'overlay', 'icbm152_2009_symmetric_for_underlay.img')'; +% Specify anatomical image for montage (not surfaces), followed +% by image name. e.g., +% o2 = canlab_results_fmridisplay([], 'overlay', ... +% 'icbm152_2009_symmetric_for_underlay.img'); % -% The default brain for overlays is the brain extracted MNI152NLin2009cAsym -% T1 1mm template from templatFlow. -% For legacy brains based on Keuken et al. 2014 enter as arguments: -% 'overlay', which('keuken_2014_enhanced_for_underlay.img') -% For legacy SPM8 single subject, enter as arguments: -% 'overlay', which('SPM8_colin27T1_seg.img') -% -% Other inputs to addblobs and render_on_surface (fmridisplay methods) are allowed, e.g., 'cmaprange', [-2 2], 'trans' +% The default brain for overlays is the brain-extracted +% MNI152NLin2009cAsym T1 1mm template from templateFlow. +% For legacy brains based on Keuken et al. 2014 enter: +% 'overlay', which('keuken_2014_enhanced_for_underlay.img') +% For legacy SPM8 single subject, enter: +% 'overlay', which('SPM8_colin27T1_seg.img') % -% See help fmridisplay -% e.g., 'color', [1 0 0] +% **'coordinates':** +% Toggle coordinates visibility on the displayed slices. % -% You can also input an existing fmridisplay object, and it will use the -% one you have created rather than setting up the canonical slices. +% Other inputs to addblobs and render_on_surface (fmridisplay methods) +% are allowed, e.g., 'cmaprange', [-2 2], 'trans', 'color', [1 0 0]. +% See help fmridisplay. % -% Try "brighten(.4) to make the images brighter. +% :Outputs: % -% :Example Script: -% :: -% -% input_activation = 'Pick_Atlas_PAL_large.nii'; -% -% % set up the anatomical overlay and display blobs -% % (see the code of this function and help fmridisplay for more examples) -% -% o2 = canlab_results_fmridisplay(input_activation); -% -% %% ========== remove those blobs and change the color ========== +% **o2:** +% fmridisplay object with the requested anatomical underlays and +% overlaid activation blobs. Reuse this object with addblobs / +% removeblobs / render_on_surface to update the display. % -% cl = mask2clusters(input_activation); -% removeblobs(o2); -% o2 = addblobs(o2, cl, 'color', [0 0 1]); +% :Examples: +% :: % -% %% ========== OR +% input_activation = 'Pick_Atlas_PAL_large.nii'; % -% r = region(input_activation); -% o2 = removeblobs(o2); -% o2 = addblobs(o2, r, 'color', [1 0 0]); +% % Set up the anatomical overlay and display blobs +% % (see the code of this function and help fmridisplay for more examples) +% o2 = canlab_results_fmridisplay(input_activation); % -% %% ========== Create empty fmridisplay object on which to add blobs: -% o2 = canlab_results_fmridisplay -% o2 = canlab_results_fmridisplay([], 'compact2', 'noverbose'); +% %% ========== remove those blobs and change the color ========== +% cl = mask2clusters(input_activation); +% removeblobs(o2); +% o2 = addblobs(o2, cl, 'color', [0 0 1]); % -% %% ========== If you want to start over with a new fmridisplay object, -% % make sure to clear o2, because it uses lots of memory +% %% ========== OR +% r = region(input_activation); +% o2 = removeblobs(o2); +% o2 = addblobs(o2, r, 'color', [1 0 0]); % -% % This image should be on your path in the "canlab_canonical_brains" subfolder: +% %% ========== Create empty fmridisplay object on which to add blobs: +% o2 = canlab_results_fmridisplay; +% o2 = canlab_results_fmridisplay([], 'compact2', 'noverbose'); % -% input_activation = 'pain-emotion_2s_z_val_FDR_05.img'; -% clear o2 -% close all -% o2 = canlab_results_fmridisplay(input_activation); +% %% ========== If you want to start over with a new fmridisplay object, +% % make sure to clear o2, because it uses lots of memory. +% % +% % This image should be on your path in the 'canlab_canonical_brains' +% % subfolder: +% input_activation = 'pain-emotion_2s_z_val_FDR_05.img'; +% clear o2 +% close all +% o2 = canlab_results_fmridisplay(input_activation); % -% %% ========== save PNGs of your images to insert into powerpoint, etc. -% % for your paper/presentation +% %% ========== Save PNGs of your images for powerpoint, etc. +% scn_export_papersetup(400); +% saveas(gcf, 'results_images/pain_meta_fmridisplay_example_sagittal.png'); % -% scn_export_papersetup(400); -% saveas(gcf, 'results_images/pain_meta_fmridisplay_example_sagittal.png'); +% scn_export_papersetup(350); +% saveas(gcf, 'results_images/pain_meta_fmridisplay_example_sagittal.png'); % -% scn_export_papersetup(350); -% saveas(gcf, 'results_images/pain_meta_fmridisplay_example_sagittal.png'); +% % Change colors, removing old blobs and replacing with new ones: +% o2 = canlab_results_fmridisplay(d, o2, 'cmaprange', [.3 .45], ... +% 'splitcolor', {[0 0 1] [.3 0 .8] [.9 0 .5] [1 1 0]}, ... +% 'outlinecolor', [.5 0 .5]); % -% Change colors, removing old blobs and replacing with new ones: -% o2 = canlab_results_fmridisplay(d, o2, 'cmaprange', [.3 .45], 'splitcolor', {[0 0 1] [.3 0 .8] [.9 0 .5] [1 1 0]}, 'outlinecolor', [.5 0 .5]); +% %% ========== Legend control +% % There is a 'nolegend' option. +% % Colorbar legends are created in render_on_surface. +% % You can access and control the handles like this: +% set(obj.activation_maps{1}.legendhandle, 'Position', ... +% [0.965 0.0994 0.01 0.4037]); % -% %% ========== Legend control -% There is a 'nolegend' option. -% Colorbar legends are created in render_on_surface -% You can access and control the handles like this: -% set(obj.activation_maps{1}.legendhandle, 'Position', [[0.965 0.0994 0.01 0.4037]]); +% %% ========== Colormap range control +% % Range is set automatically by default, and stored in +% % obj.activation_maps{wh_to_display}.cmaprange. +% % You can enter 'cmaprange', followed by inputs in the correct +% % format, to manually control this. % -% %% ========== Colormap range control -% Range is set automatically by default, and stored in -% obj.activation_maps{wh_to_display}.cmaprange -% You can enter 'cmaprange', followed by inputs in the correct format, to -% manually control this. -% -% .. -% Tor Wager -% 1/27/2012 -% .. +% :See also: +% - fmridisplay +% - addblobs +% - removeblobs +% - render_on_surface +% - region +% - fmri_data +% - statistic_image % Toggle coordinates visibility coordinates = 0; diff --git a/CanlabCore/Visualization_functions/getVertexColors_old_backup.m b/CanlabCore/Visualization_functions/getVertexColors_old_backup.m deleted file mode 100644 index 91a62d58..00000000 --- a/CanlabCore/Visualization_functions/getVertexColors_old_backup.m +++ /dev/null @@ -1,408 +0,0 @@ -% :Usage: -% :: -% -% [c, alld] = getVertexColors(xyz, v, actcolor, [basecolor], [mind], 'vert', [xyz2], [actcolor2], 'vert', [xyz3], [actcolor3]) -% -% given a point list of XYZ mm coordinates (3 columns) -% and a list of vertices in an isosurface, -% returns FaceVertexCData color values for brain near points and brain not near points. -% c is vertex color specification, 3 columns indicating RGB values -% -% Inputs: -% xyz a 3-vol list of vertices to color -% v can be a matrix of vertices -% or a handle to a patch object containing vertices -% if it's a handle, this function sets the color to interp -% and the FaceVertexCData to the color matrix c -% actcolor -% [r g b] activation color -% basecolor -% [r g b] baseline color - optional. -% mind optional - min distance to color vertex -% Vertices within mind of an xyz coordinate will be colored -% colorscale -% optional. followed by vector of values by which to multiply input -% color -% these are scaled to be between .3 and one. -% if entered, this will make the colors vary by, for example, Z score -% so Z-scores are an acceptable input. -% cscale should be in the same coordinate order as xyz -% for ADDITIONAL clusters, repeat the 'colorscale', Z argument pair in the function call -% -% YOU CAN ALSO pass true RGB values for each xyz coordinate in: 'colorscale', rgblist, -% IF cscale is a 3-vector, it specifies the ACTUAL colors, and is not scaled to .3 - 1 -% -% -% following basecolor and mind: -% additional xyz coordinate lists, with syntax: -% 'vert', xyz2 [your xyz input], [r g b] color for xyz plot -% -% also, you can enter 'ovlcolor' followed by [r g b] for overlaps between xyz sets -% colors will ONLY appear in the overlap color if they share actual coordinates in common, -% not necessarily if surface vertices are within the specified distance from both sets of coords. -% -% to get a good brain surface, try this: -%figure -%p = patch('Faces', faces, 'Vertices', vertices, 'FaceColor', [.5 .5 .5], ... -% 'EdgeColor', 'none', 'SpecularStrength', .2, 'FaceAlpha', 1, 'SpecularExponent', 200); -%lighting gouraud;camlight right -%axis image; myLight = camlight(0, 0);set(myLight, 'Tag', 'myLight'); -%set(gcf, 'WindowButtonUpFcn', 'lightFollowView');lightfollowview -%drawnow -% -% .. -% by Tor Wager August 25, 2002 -% .. - - -function [c, alld] = getVertexColors_old_backup(xyz, v, actcolor, varargin) - - mind = 3; - basecolor = [.5 .5 .5]; - alld = []; - xyza = xyz; - cscale = []; - allda = []; - vv = []; - - % ----------------------------------------------------------------------- - % * set up input arguments - % ----------------------------------------------------------------------- - doalph = 0; cscale = []; - if ~isempty(varargin), basecolor = varargin{1}; end - if length(varargin) > 1, mind = varargin{2}; end - ind = 1; - for i = 3:length(varargin) - if strcmp(varargin{i}, 'vert') - vv{ind} = varargin{i+1}; - - % intersections - xyzb{ind, 1} = intersect(xyz, vv{ind}, 'rows'); - for j = ind-1:-1:1 - xyzb{ind, j} = intersect(vv{j}, vv{ind}, 'rows'); - xyza = intersect(xyza, xyzb{ind, j}, 'rows'); - end - - cc{ind} = varargin{i+2}; - ind = ind+1; - elseif strcmp(varargin{i}, 'ovlcolor') - ocol = varargin{i+1}; - elseif strcmp(varargin{i}, 'alphaone') - doalph = 1; - elseif strcmp(varargin{i}, 'allcolor') - acol = varargin{i+1}; - elseif strcmp(varargin{i}, 'colorscale') - cscale{end+1} = varargin{i+1}; - if min(size(cscale{end})) == 1 - % scale colors (may be necessary) - only if single vector, not RGB values - cscale{end} = cscale{end} ./ max(cscale{end}); - end - if any(cscale{end} < 0), error('Some color scale values are less than zero.'), end - - end - end - - if isempty(cscale) - cscale{1} = ones(size(xyz, 1), 1); - for i = 1:length(vv) % additional vertices - cscale{end+1} = ones(size(vv{i}, 1), 1); - end - end - - %if ~isempty(cscale) don't need this. - % if length(cscale) < length(vv) - % cscale{length(vv)} = []; - % end - %end - - - if ishandle(v) - p = v; - v = get(p, 'Vertices'); - c = get(p, 'FaceVertexCData'); % get existing colors from surface - else - % uh-oh, not a handle! - warning('Figure handle missing: Figure was closed?') - p = findobj('Type', 'patch'); - v = get(p(1), 'Vertices'); - c = get(p, 'FaceVertexCData'); % get existing colors from surface - end - - bad = any(size(c) - size(v)); - if bad - c = repmat(basecolor, size(v, 1), 1); - end - - - - - % ----------------------------------------------------------------------- - % * main xyz color change - % ----------------------------------------------------------------------- - - t1 = clock; - fprintf('Main color vertices: ') - - c = change_colors(c, xyz, v, mind, cscale, actcolor, p); - drawnow(); - - - - % ----------------------------------------------------------------------- - % * additional optional vertices - % ----------------------------------------------------------------------- - if exist('vv', 'var') - for j = 1:length(vv) - fprintf('\nAdditional vertices: ') - - % cscale: - % pass in vector of ones length coords for solid color - % or scalar vals for color mapping - % pass in cell array - - % cc{j} is color, cscale{j+1} is scaling vals for coords - c = change_colors(c, vv{j}, v, mind, cscale(j+1), cc{j}, p); - end - drawnow(); - end - - - % ----------------------------------------------------------------------- - % * figure out which vertices should be colored with ocol (overlap color) - % ----------------------------------------------------------------------- - - if exist('ocol', 'var') && exist('xyzb', 'var') && ~isempty(cat(1, xyzb{:})) - - alld = zeros(size(v, 1), 1); % keeps track of overlap vertices - xyzb = cat(1, xyzb{:}); - - t1 = clock; - fprintf('\nOverlap vertices: ') - - cscaletmp = {ones(size(xyzb, 1), 1)}; - c = change_colors(c, xyzb, v, mind, cscaletmp, ocol, p); - drawnow(); - - fprintf('%3.0f.done in %3.0f s\n', i, etime(clock, t1)) - end - - - % ----------------------------------------------------------------------- - % * figure out which vertices should be colored with acol (all color) - % ----------------------------------------------------------------------- - if exist('acol', 'var') && ~isempty(xyza) && length(vv)>1 - - xyzall = xyza; - for i = 2:length(vv) - xyzall = intersect(xyzall, vv{i}, 'rows'); - end - - t1 = clock; - fprintf('\nAll overlap vertices: ') - - cscaletmp = {ones(size(xyzall, 1), 1)}; - c = change_colors(c, xyzall, v, mind, cscaletmp, acol, p); - drawnow(); - - fprintf('%3.0f.done in %3.0f s\n', i, etime(clock, t1)) - - end - - - % ----------------------------------------------------------------------- - % * final color change - % ----------------------------------------------------------------------- - - - if exist('p', 'var') - set(p, 'FaceColor', 'interp') - set(p, 'FaceVertexCData', c) - drawnow() - end - - %lightFollowView -end - - - - - - -% ----------------------------------------------------------------------- -% * SUB-FUNCTIONS -% ----------------------------------------------------------------------- - - - -function c = change_colors(c, coords, v, mind, cscale, actcolor, p) - - if isempty(coords), disp('Coords is empty. Nothing to plot.'), return, end - - % select vertices that are even close - cmax = max(coords, [], 1); - cmin = min(coords, [], 1); - fprintf('%3.0f vertices. selecting: ', size(v, 1)); - wh = any(v - repmat(cmax, size(v, 1), 1) > mind, 2); - wh2 = any(repmat(cmin, size(v, 1), 1) - v > mind, 2); - - % list vertices to test and possibly change color - whverts = (1:size(v, 1))'; - whverts(wh | wh2) = []; % vertex indices in big list - smallv = v; - smallv(wh | wh2,:) = []; % vertices--restricted list - - fprintf('%3.0f\n', size(whverts, 1)); - - if isempty(smallv), return, end - - % select coords that are even close - % ---------------------------------- - cmax = max(smallv, [], 1); - cmin = min(smallv, [], 1); - fprintf('%3.0f coords. selecting: ', size(coords, 1)); - wh = any(coords - repmat(cmax, size(coords, 1), 1) > mind, 2); - wh2 = any(repmat(cmin, size(coords, 1), 1) - coords > mind, 2); - - - % if cscale is matrix, must select these values of cscale as well! - if size(cscale{1}, 1) == size(coords, 1) - cscale{1}(wh | wh2,:) = []; - end - - coords(wh | wh2,:) = []; - - - if isempty(coords), return, end - - nc = size(coords, 1); - fprintf('%3.0f\n', nc); - - % break up coords into list and run - % ---------------------------------- - - % break up coords into list - xyz2 = {}; indx = 1; - for kk = 1:1000:nc - setwh{indx} = (kk:min(nc, kk + 1000 - 1))'; - xyz2{indx} = coords(setwh{indx},:); - - indx = indx + 1; - end - - fprintf('Running %3.0f sets of coordinates: 000', length(xyz2)); - - indxval = 1; - wh_coords_near_surface = false(size(coords, 1), 1); - - for setno = 1:length(xyz2) - fprintf('\b\b\b%03d', setno); - - for i = 1:size(xyz2{setno}, 1) - % find vertices that are within range of point i in set setno - vertex_indices = find_in_radius(xyz2, setno, i, smallv, mind, whverts); - - % two modes: if cscale{1} is a matrix, treats as rgb values, and put in - % color stored in cscale. if cscale{1} is a vector, treat it as a scaling value for actcolor - % In either case, indxval should index location of coordinate in FULL - % list (corresponding to full list in cscale) - - c = color_change_vertices(c, mind, indxval, cscale, actcolor, vertex_indices); - - if ~isempty(vertex_indices), wh_coords_near_surface(indxval) = 1; end - - indxval = indxval + 1; - end - - if exist('p', 'var') - set(p, 'FaceColor', 'interp') - set(p, 'FaceVertexCData', c) - end - end -end - - - - - - -function z = dist_tmp(w, p) - - % - % if isstr(w) - % switch (w) - % case 'deriv', - % z = ''; - % otherwise - % error('Unrecognized code.') - % end - % return - % end - - % CALCULATION - if nargin == 1 - p = w; - w = w'; - end - - [S, R] = size(w); - [R2, Q] = size(p); - if (R ~= R2), error('Inner matrix dimensions do not match.'), end - - z = zeros(S, Q); - if (Q 1 % if we have rgb values rather than scaling values - % this occurs if heatmap = yes and colorscale = no, we pass in rgb values - c(vertex_indices,:) = repmat(cscale{1}(i,:), n, 1); - else - c(vertex_indices,:) = repmat(actcolor.*cscale{1}(i,:), n, 1); - end - end - -end - diff --git a/CanlabCore/Visualization_functions/getVertexColors_old_backup2.m b/CanlabCore/Visualization_functions/getVertexColors_old_backup2.m deleted file mode 100644 index 16ba5524..00000000 --- a/CanlabCore/Visualization_functions/getVertexColors_old_backup2.m +++ /dev/null @@ -1,540 +0,0 @@ -function [c, alld] = getVertexColors(xyz, v, actcolor, varargin) -% :Usage: -% :: -% -% [c, alld] = getVertexColors(xyz, v, actcolor, [basecolor], [mind], 'vert', [xyz2], [actcolor2], 'vert', [xyz3], [actcolor3]) -% -% given a point list of XYZ mm coordinates (3 columns) -% and a list of vertices in an isosurface, -% returns FaceVertexCData color values for brain near points and brain not near points. -% c is vertex color specification, 3 columns indicating RGB values -% -% :Inputs: -% -% **xyz:** -% a 3-vol list of vertices to color -% -% **v:** -% can be a matrix of vertices -% or a handle to a patch object containing vertices -% if it's a handle, this function sets the color to interp -% and the FaceVertexCData to the color matrix c -% -% **actcolor:** -% [r g b] activation color -% -% **basecolor:** -% [r g b] baseline color - optional. -% -% **mind:** -% optional - min distance to color vertex -% Vertices within mind of an xyz coordinate will be colored -% -% **colorscale -% optional. followed by vector of values by which to multiply input -% color -% these are scaled to be between .3 and one. -% -% if entered, this will make the colors vary by, for example, Z score -% so Z-scores are an acceptable input. -% -% cscale should be in the same coordinate order as xyz -% -% for ADDITIONAL clusters, repeat the 'colorscale', Z argument pair in the function call -% -% YOU CAN ALSO pass true RGB values for each xyz coordinate in: 'colorscale', rgblist, -% IF cscale is a 3-vector, it specifies the ACTUAL colors, and is not scaled to .3 - 1 -% -% Following basecolor and mind: -% -% additional xyz coordinate lists, with syntax: -% vert', xyz2 [your xyz input], [r g b] color for xyz plot -% -% also, you can enter 'ovlcolor' followed by [r g b] for overlaps between xyz sets -% colors will ONLY appear in the overlap color if they share actual coordinates in common, -% not necessarily if surface vertices are within the specified distance from both sets of coords. -% -% :Examples: -% :: -% -% % to get a good brain surface, try this: -% figure -% p = patch('Faces', faces, 'Vertices', vertices, 'FaceColor', [.5 .5 .5], ... -% å 'EdgeColor', 'none', 'SpecularStrength', .2, 'FaceAlpha', 1, 'SpecularExponent', 200); -% lighting gouraud; -% camlight right -% axis image; -% myLight = camlight(0, 0); -% set(myLight, 'Tag', 'myLight'); -% set(gcf, 'WindowButtonUpFcn', 'lightFollowView'); -% lightfollowview -% drawnow -% -% .. -% by Tor Wager August 25, 2002 -% .. - - - global do_fsavg_right do_fsavg_left ras - - mind = 3; - basecolor = [.5 .5 .5]; - alld = []; - xyza = xyz; - cscale = []; - allda = []; - vv = []; - doalph = 0; - cscale = []; - alphascale = {}; - do_fsavg_left = false; - do_fsavg_right = false; - doverbose = true; - - % ----------------------------------------------------------------------- - % * set up input arguments - % ----------------------------------------------------------------------- - - if ~isempty(varargin), basecolor = varargin{1}; end - - if length(varargin) > 1, mind = varargin{2}; end - - ind = 1; - for i = 3:length(varargin) - if strcmp(varargin{i}, 'vert') - vv{ind} = varargin{i+1}; - - % intersections - xyzb{ind, 1} = intersect(xyz, vv{ind}, 'rows'); - for j = ind-1:-1:1 - xyzb{ind, j} = intersect(vv{j}, vv{ind}, 'rows'); - xyza = intersect(xyza, xyzb{ind, j}, 'rows'); - end - - cc{ind} = varargin{i+2}; - ind = ind+1; - elseif strcmp(varargin{i}, 'ovlcolor') - ocol = varargin{i+1}; - - elseif strcmp(varargin{i}, 'alphascale') - doalph = 1; - alphascale{1} = varargin{i + 1}; - - elseif strcmp(varargin{i}, 'allcolor') - acol = varargin{i+1}; - - elseif strcmp(varargin{i}, 'colorscale') - - cscale{end+1} = varargin{i+1}; - - % now do this ahead of time - % if min(size(cscale{end})) == 1 - % % scale colors (may be necessary) - only if single vector, not RGB values - % cscale{end} = cscale{end} ./ max(cscale{end}); - % end - if any(cscale{end} < 0), error('Some color scale values are less than zero.'), end - elseif strcmp(varargin{i}, 'fsavg_left') - ras = load(which('lh.avgMapping_allSub_RF_ANTs_MNI152_orig_to_fsaverage.mat')); - do_fsavg_left = true; - elseif strcmp(varargin{i}, 'fsavg_right') - % uses freesurfer inflated brain with Thomas Yeo group's RF_ANTs mapping - % from MNI to Freesurfer. (https://doi.org/10.1002/hbm.24213) - ras = load(which('rh.avgMapping_allSub_RF_ANTs_MNI152_orig_to_fsaverage.mat')); - do_fsavg_right = true; - - elseif strcmp(varargin{i}, 'noverbose') - doverbose = false; - - end - end - - if isempty(cscale) - cscale{1} = ones(size(xyz, 1), 1); - for i = 1:length(vv) % additional vertices - cscale{end+1} = ones(size(vv{i}, 1), 1); - end - end - - %if ~isempty(cscale) don't need this. - % if length(cscale) < length(vv) - % cscale{length(vv)} = []; - % end - %end - - - if ishandle(v) - p = v; - v = get(p, 'Vertices'); - c = get(p, 'FaceVertexCData'); % get existing colors from surface - else - % uh-oh, not a handle! - warning('Figure handle missing: Figure was closed?') - p = findobj('Type', 'patch'); - v = get(p(1), 'Vertices'); - c = get(p, 'FaceVertexCData'); % get existing colors from surface - end - - - % if FaceVertexCData is v x 1 matrix, it is mapped to the figure colormap - % if it is v x 3, it is true color. - % We can replace the colormapped FaceVertexCData with its true-color - % equivalent, making it colormap independent. - % This will allow us to use the existing values and average in new colors - % for blobs - if ishandle(p) && size(v,1) == size(c, 1) && size(c, 2) == 1 - cm = get(gcf, 'Colormap'); - c2 = round((c./max(c)) .* length(cm)); - c2(c2==0)=1; - c2 = cm(c2, :); - c = c2; % this is the true-color equivalent - clear cm c2 - set(p, 'FaceVertexCData', c); - end - - bad = any(size(c) - size(v)); % this could occur for various reasons? - - if bad - c = repmat(basecolor, size(v, 1), 1); - end - -% c = true-color vertices x 3 -% v = xyz coords of vertices x 3 -% cscale: mapped true colors or index for xyz, cell with cscale{1} = xyz coords to color x 3 or x 1 - - % ----------------------------------------------------------------------- - % * main xyz color change - % ----------------------------------------------------------------------- - - t1 = clock; - if doverbose, fprintf('Main color vertices: '), end - - c = change_colors(c, xyz, v, mind, cscale, actcolor, p, alphascale, doverbose); - drawnow(); - - - - % ----------------------------------------------------------------------- - % * additional optional vertices - % ----------------------------------------------------------------------- - if exist('vv', 'var') - for j = 1:length(vv) - if doverbose, fprintf('\nAdditional vertices: '), end - - % cscale: - % pass in vector of ones length coords for solid color - % or scalar vals for color mapping - % pass in cell array - - % cc{j} is color, cscale{j+1} is scaling vals for coords - c = change_colors(c, vv{j}, v, mind, cscale(j+1), cc{j}, p, alphascale, doverbose); - end - drawnow(); - end - - - % ----------------------------------------------------------------------- - % * figure out which vertices should be colored with ocol (overlap color) - % ----------------------------------------------------------------------- - - if exist('ocol', 'var') && exist('xyzb', 'var') && ~isempty(cat(1, xyzb{:})) - - alld = zeros(size(v, 1), 1); % keeps track of overlap vertices - xyzb = cat(1, xyzb{:}); - - t1 = clock; - if doverbose, fprintf('\nOverlap vertices: '), end - - cscaletmp = {ones(size(xyzb, 1), 1)}; - c = change_colors(c, xyzb, v, mind, cscaletmp, ocol, p, alphascale, doverbose); - drawnow(); - - if doverbose, fprintf('%3.0f.done in %3.0f s\n', i, etime(clock, t1)), end - end - - - % ----------------------------------------------------------------------- - % * figure out which vertices should be colored with acol (all color) - % ----------------------------------------------------------------------- - if exist('acol', 'var') && ~isempty(xyza) && length(vv)>1 - - xyzall = xyza; - for i = 2:length(vv) - xyzall = intersect(xyzall, vv{i}, 'rows'); - end - - t1 = clock; - if doverbose, fprintf('\nAll overlap vertices: '), end - - cscaletmp = {ones(size(xyzall, 1), 1)}; - c = change_colors(c, xyzall, v, mind, cscaletmp, acol, p, alphascale, doverbose); - drawnow(); - - if doverbose, fprintf('%3.0f.done in %3.0f s\n', i, etime(clock, t1)), end - - end - - - % ----------------------------------------------------------------------- - % * final color change - % ----------------------------------------------------------------------- - - - if exist('p', 'var') - set(p, 'FaceColor', 'interp') - set(p, 'FaceVertexCData', c) - drawnow() - end - - %lightFollowView -end - - - - - - -% ----------------------------------------------------------------------- -% * SUB-FUNCTIONS -% ----------------------------------------------------------------------- - - - -function c = change_colors(c, coords, v, mind, cscale, actcolor, p, alphascale, doverbose) - -% c is list of colors. by default, usually [.5 .5 .5] for all colors -% (basecolor) - global do_fsavg_left do_fsavg_right ras - - if isempty(coords), if doverbose, disp('Coords is empty. Nothing to plot.'), end, return, end - - % select surface vertices that are close to coords - % vertices that are far will be omitted - % smallv is a reduced set of vertices - % -------------------------------------------------------------------- - cmax = max(coords, [], 1); - cmin = min(coords, [], 1); - if doverbose, fprintf('%3.0f vertices. selecting: ', size(v, 1)); end - - wh = any(v - repmat(cmax, size(v, 1), 1) > mind, 2); - wh2 = any(repmat(cmin, size(v, 1), 1) - v > mind, 2); - - % list vertices to test and possibly change color - whverts = (1:size(v, 1))'; - whverts(wh | wh2) = []; % indices of smallv in big (original) list - if do_fsavg_left || do_fsavg_right - smallv = ras.ras'; - else - smallv = v; - end - smallv(wh | wh2,:) = []; % vertices--restricted list - - if doverbose, fprintf('%3.0f\n', size(whverts, 1)); end - - if isempty(smallv), return, end - - % select coords that are close to surface vertices5 - % coords, cscale{1}, and alphascale{1} all have same indices - % --------------------------------------------------------------------- - cmax = max(smallv, [], 1); - cmin = min(smallv, [], 1); - if doverbose, fprintf('%3.0f coords. selecting: ', size(coords, 1)); end - - % omit wh and wh2 - outside scope of this coord set - wh = any(coords - repmat(cmax, size(coords, 1), 1) > mind, 2); - wh2 = any(repmat(cmin, size(coords, 1), 1) - coords > mind, 2); - - % if cscale is matrix, must select these values of cscale as well! - if length(cscale) > 0 && size(cscale{1}, 1) == size(coords, 1) - cscale{1}(wh | wh2,:) = []; - end - - if length(alphascale) > 0 && size(alphascale{1}, 1) == size(coords, 1) - alphascale{1}(wh | wh2,:) = []; - end - - coords(wh | wh2,:) = []; - - if isempty(coords), return, end - - nc = size(coords, 1); - if doverbose, fprintf('%3.0f\n', nc); end - - % break up coords into list and run - % ---------------------------------- - - % break up coords into list - xyz2 = {}; indx = 1; - for kk = 1:1000:nc - setwh{indx} = (kk:min(nc, kk + 1000 - 1))'; - xyz2{indx} = coords(setwh{indx},:); - - indx = indx + 1; - end - - if doverbose, fprintf('Running %3.0f sets of coordinates: 000', length(xyz2)); end - - indxval = 1; - wh_coords_near_surface = false(size(coords, 1), 1); - - for setno = 1:length(xyz2) - if doverbose, fprintf('\b\b\b%03d', setno); end - - for i = 1:size(xyz2{setno}, 1) - % i indexes coordinate; e.g., 1000 iterations for a typical - % coord set - % find vertices that are within range of point i in set setno - % vertex_indices are indices into the ORIGINAL list - vertex_indices = find_in_radius(xyz2, setno, i, smallv, mind, whverts); - - % two modes: - % - if cscale{1} is a matrix, treats as rgb values, and put in - % color stored in cscale. actcolor is ignored. - % - if cscale{1} is a vector, treat it as a scaling value for actcolor - % In either case, indxval should index location of coordinate in FULL - % list (corresponding to full list in cscale) - - % cscale{1}(i) and alphascale{1}(i) contain unique values for each coord i - % these are used to map colors for single point i to multiple - % nearby vertices vertex_indices - - if length(cscale) > 0 && ~isempty(cscale{1}) - mycscale = cscale{1}(setwh{setno}(i), :); % color for this point, index into full list of coords/scalevals - else - mycscale = []; - end - - if length(alphascale) > 0 && ~isempty(alphascale{1}) - myalphascale = alphascale{1}(setwh{setno}(i), :); % alpha for this point, index into full list of coords/scalevals - - % adjust by cube of mind, to adjust for overlap in vertices - % affected by adjacent coords - done now in cluster_surf - %myalphascale = myalphascale ./ mind^3; - - else - myalphascale = []; - end - - c = color_change_vertices(c, mycscale, actcolor, vertex_indices, myalphascale); - - if ~isempty(vertex_indices), wh_coords_near_surface(indxval) = 1; end - - indxval = indxval + 1; - end - end % setno - - if exist('p', 'var') - set(p, 'FaceColor', 'interp') - set(p, 'FaceVertexCData', c) - drawnow - end - -end % change_colors - - - - - - -function z = dist_tmp(w, p) - - % - % if isstr(w) - % switch (w) - % case 'deriv', - % z = ''; - % otherwise - % error('Unrecognized code.') - % end - % return - % end - - % CALCULATION - if nargin == 1 - p = w; - w = w'; - end - - [S, R] = size(w); - [R2, Q] = size(p); - if (R ~= R2), error('Inner matrix dimensions do not match.'), end - - z = zeros(S, Q); - if (Q 1 - % if we have rgb values rather than scaling values - % this occurs if heatmap = yes and colorscale = no, we pass in rgb values - % actcolor is ignored - - if isempty(myalphascale) - % solid colors - c(vertex_indices,:) = repmat(mycscale, n, 1); - else - w = myalphascale; % the weight for new color vs. old. - c(vertex_indices, :) = (1-w) .* c(vertex_indices, :) + w .* repmat(mycscale, n, 1); - end - - else - % this will scale the activation color in proportion to cscale - % for each voxel - - w = mycscale; % the weight for new color vs. old. 1 = all new, 0 = all old - c(vertex_indices, :) = (1-w) .* c(vertex_indices, :) + w .* repmat(actcolor, n, 1); - end -end - -end - diff --git a/CanlabCore/Visualization_functions/montage_clusters.m b/CanlabCore/Visualization_functions/montage_clusters.m index 6dd0e4ca..d1e80891 100644 --- a/CanlabCore/Visualization_functions/montage_clusters.m +++ b/CanlabCore/Visualization_functions/montage_clusters.m @@ -719,16 +719,13 @@ function display_underlay(mch, oimg, whsl, rc, V, textx, texty) end switch spm('Ver') - case 'SPM8' - % no analyze field; flip is always 1 - case {'SPM5', 'SPM2', 'SPM99'} if defaults.analyze.flip disp('Warning: Setting defaults.analyze.flip to 0. No flipping.') defaults.analyze.flip = 0; end otherwise - warning('Unknown version of SPM. Results may be erratic.') + % SPM8+: no analyze field; flip is always 1 end end diff --git a/CanlabCore/Visualization_functions/montage_clusters_medial.m b/CanlabCore/Visualization_functions/montage_clusters_medial.m index 7bcb52ad..7b41761a 100644 --- a/CanlabCore/Visualization_functions/montage_clusters_medial.m +++ b/CanlabCore/Visualization_functions/montage_clusters_medial.m @@ -645,16 +645,13 @@ end switch spm('Ver') - case 'SPM8' - % no analyze field; flip is always 1 - case {'SPM5', 'SPM2', 'SPM99'} if defaults.analyze.flip disp('Warning: Setting defaults.analyze.flip to 0. No flipping.') defaults.analyze.flip = 0; end otherwise - warning('Unknown version of SPM. Results may be erratic.') + % SPM8+: no analyze field; flip is always 1 end end diff --git a/CanlabCore/Visualization_functions/scn_export_papersetup.m b/CanlabCore/Visualization_functions/scn_export_papersetup.m index 00561cd0..66eedac5 100644 --- a/CanlabCore/Visualization_functions/scn_export_papersetup.m +++ b/CanlabCore/Visualization_functions/scn_export_papersetup.m @@ -1,15 +1,48 @@ function scn_export_papersetup(minsize) +% scn_export_papersetup Set paper size of current figure for export to image files. +% % :Usage: % :: % -% scn_export_papersetup([opt: min size in pixels, default = 400]) +% scn_export_papersetup([minsize]) % -% set paper size for current figure so that print to png or tiff looks as -% it should (as it does on-screen) +% Set the paper size for the current figure so that printing to PNG or +% TIFF (e.g., via saveas, print, or export_fig) produces output that +% matches what is shown on-screen. The figure aspect ratio is preserved, +% and the smaller dimension of the figure is scaled to minsize +% pixels (default 400). % % .. % tor wager, aug. 06 % .. +% +% :Inputs: +% +% **minsize:** +% Optional. Minimum size in pixels for the smaller of the figure's +% width and height. Default = 400. The larger dimension is scaled +% proportionally so the original aspect ratio is preserved. +% +% :Outputs: +% +% None. The function modifies the 'PaperUnits', 'PaperPosition', and +% 'PaperType' properties of the current figure (gcf) in place. +% +% :Examples: +% :: +% +% % Set up paper size and save the current figure as a PNG +% scn_export_papersetup(400); +% saveas(gcf, 'my_figure.png'); +% +% % Larger paper size for higher-resolution exports +% scn_export_papersetup(800); +% print(gcf, '-dpng', '-r300', 'my_figure_hires.png'); +% +% :See also: +% - saveas +% - print +% - canlab_results_fmridisplay if nargin < 1, minsize = 400; end diff --git a/CanlabCore/diagnostics/scnlab_norm_check.m b/CanlabCore/diagnostics/scnlab_norm_check.m index 70166943..bcc4e5cd 100644 --- a/CanlabCore/diagnostics/scnlab_norm_check.m +++ b/CanlabCore/diagnostics/scnlab_norm_check.m @@ -138,18 +138,7 @@ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% function parse_inputs() - switch(spm('Ver')) - case {'SPM5' 'SPM8' 'SPM12'} - mask = which('avg152T1.nii'); - if isempty(mask) - mask = spm_select(1, 'image', 'Cannot find brain mask: please select', [], pwd()); - end - if(~exist('template', 'var') || isempty(template)) %#ok - template = spm_select(1, 'image', 'No template image passed in: please select', [], pwd()); - end - if(~exist('wanat_files', 'var') || isempty(wanat_files)) %#ok - wanat_files = spm_select(Inf, 'image', 'No subjects'' normalized anatomical images passed in: please select', [], pwd(), 'w.*'); - end + switch spm('Ver') case 'SPM2' mask = which('avg152T1.img'); if isempty(mask) @@ -162,7 +151,17 @@ function parse_inputs() wanat_files = spm_get(Inf, 'w*img', 'No subjects'' normalized anatomical images passed in: please select'); end otherwise - error('Unknown SPM version: %s\n', spm('Ver')); + % SPM5+, including any future versions + mask = which('avg152T1.nii'); + if isempty(mask) + mask = spm_select(1, 'image', 'Cannot find brain mask: please select', [], pwd()); + end + if(~exist('template', 'var') || isempty(template)) %#ok + template = spm_select(1, 'image', 'No template image passed in: please select', [], pwd()); + end + if(~exist('wanat_files', 'var') || isempty(wanat_files)) %#ok + wanat_files = spm_select(Inf, 'image', 'No subjects'' normalized anatomical images passed in: please select', [], pwd(), 'w.*'); + end end if(~exist('mean_func_files', 'var')) diff --git a/CanlabCore/diagnostics/scnlab_pca_denoise_session.m b/CanlabCore/diagnostics/scnlab_pca_denoise_session.m index 6f4c8a8e..3414aabb 100644 --- a/CanlabCore/diagnostics/scnlab_pca_denoise_session.m +++ b/CanlabCore/diagnostics/scnlab_pca_denoise_session.m @@ -67,7 +67,10 @@ function scnlab_pca_denoise_session(image_names, basename, nuisX, designX, varar compscore = compscore(:,1:k); fprintf('Writing pca_spatial%s 4-D images.\n', img_ext); - if ~strcmpi(spm('Ver'), 'spm5'), disp('Warning! Unless you use SPM5 or above, all components will not be saved/viewable.'); end + % Warn only on legacy SPM2/SPM99; SPM5+ all support saving components. + if any(strcmpi(spm('Ver'), {'spm2', 'spm99'})) + disp('Warning! Unless you use SPM5 or above, all components will not be saved/viewable.'); + end % reconstruction and writing of spatial maps iimg_reconstruct_vols(eigvec, maskInfo, 'outname', [basename '_pca_spatial' img_ext]); diff --git a/CanlabCore/fmridisplay_helper_functions/render_blobs.m b/CanlabCore/fmridisplay_helper_functions/render_blobs.m index f7719116..12dc13c6 100644 --- a/CanlabCore/fmridisplay_helper_functions/render_blobs.m +++ b/CanlabCore/fmridisplay_helper_functions/render_blobs.m @@ -221,13 +221,14 @@ exclusive{1})); end - interpInd = find(strcmp('interp',varargin)); + interpInd = find(strcmp('interp', varargin)); if isempty(interpInd) warning('Indexmap requires ''interp'',''nearest'' but these were not specified. Adding them automatically'); varargin{end+1} = 'interp'; varargin{end+1} = 'nearest'; interpStyle = 'nearest'; - elseif ~strcmp(varargin(interpInd+1),'nearest') + + elseif ~strcmp(varargin(interpInd+1), 'nearest') warning(sprintf('Indexmap requires ''interp'',''nearest'' but ''intep'',%s was specified instead. Automatically changing to ''nearest''',varargin{interpInd+1})); varargin{interpInd+1} = 'nearest'; interpStyle = 'nearest'; @@ -241,6 +242,7 @@ catch error('''indexmap'' argument must be followed by an n x 3 matrix of colormap values'); end + case 'colormap' for exclusive = {'color','maxcolor','mincolor','onecolor','splitcolor','contour','outline'} @@ -255,9 +257,25 @@ dosplitcolor = 0; try cm=varargin{i+1}; + try % behavior was changed; intention was to use default colormap + % if colormap was entered without subsequent + % argument specifying values. + validateattributes(cm, 'numeric', {'2d'}) + catch + % set default colormap based on values + u = unique(currentmap.mapdata(:)); + if all(u <= 0) + cm = colormap_tor(mincolor, mincolor ./2); + elseif all(u >= 0) + cm = colormap_tor(maxcolor ./ 2, maxcolor); + else + cm = colormap_tor(mincolor, maxcolor); + end + end catch error('''colormap'' argument must be followed by an n x 3 matrix of colormap values'); end + case 'splitcolor' docolormap = 1; dosplitcolor = 1; @@ -301,8 +319,8 @@ case 'coronal', myview = 'coronal'; %disp('Warning! NOT implemented correctly yet!!!'), pause(5) case 'axial', myview = 'axial'; - case {'wh_montages', 'regioncenters', 'blobcenters', 'nosymmetric', 'compact2', 'nooutline','no_surface', 'nolegend', ... - 'colormap', 'solid', 'thresh', 'k', 'nofigure' 'wh_surfaces' 'montagetype' 'sourcespace' 'targetsurface' 'compact3', ... + case {'wh_montages', 'regioncenters', 'blobcenters', 'nosymmetric', 'nooutline','no_surface', 'nolegend', ... + 'solid', 'thresh', 'k', 'nofigure' 'wh_surfaces' 'montagetype' 'sourcespace' 'targetsurface' , ... 'disableVis3d'} % not functional, avoid warning % these are passed in to allow flexible functionality in @@ -317,18 +335,13 @@ k = varargin{i+1}; enhance_contrast = @(x1)((1./(1+exp(-k.*x1)))-0.5); - case {'full','full hcp','full2','nearest', 'interp', 'MNI152NLin2009cAsym'} + case {'full','full hcp','full2','nearest', 'MNI152NLin2009cAsym'} continue case { 'compact', ... 'compact2', ... 'compact3', ... - 'full', ... 'multirow', ... - 'coronal', ... - 'sagittal', ... - 'full2', ... - 'full hcp', ... 'full hcp inflated', ... 'hcp inflated', ... 'freesurfer inflated', ... @@ -552,22 +565,27 @@ end Z = interp2(myx, myy, slicedat, mynewx, mynewy, interpStyle); % Wani modified this line. 08/11/12 + Z(isnan(Z)) = 0; % tor: 5/4/2026 to fix bug introduced in customcolors + % bogdan: when we plot multiple blobs the interpolation call above % can't adjudicate between them and we get overlaps among % neighbors that privilege latter calls to render_blobs. Given - % the way this is designed, there's no perfect soluiton because - % you need to gie render_blobs information on all blobs to + % the way this is designed, there's no perfect solution because + % you need to give render_blobs information on all blobs to % render simultaneously, but blob information is % compartamentalized. We can however improve on the situation % by masking out based on magnitude of partial volume effects, % which is what we do here. slicemask = slicedat; slicemask(slicedat~=0)=1; - if strcmp(interpStyle,'nearest'); + if strcmp(interpStyle,'nearest') Zmask = interp2(myx, myy, slicemask, mynewx, mynewy, 'linear'); else Zmask = interp2(myx, myy, slicemask, mynewx, mynewy, interpStyle); end + + Zmask(isnan(Zmask)) = 0; % tor: 5/4/2026 to fix bug introduced in customcolors + Z(Zmask < partial_vol_thresh) = 0; if dosmooth @@ -670,11 +688,13 @@ w = repmat(Zscaled, [1 1 3]); slicecdat = (w .* cdat) + (1 - w) .* cdat2; + elseif customcolormap %w = repmat(Z, [1 1 3]); w = map_function(Z,cmaprange(1),cmaprange(2),1,size(cm,1)); slicecdat = reshape(cm(round(w),:),[size(Z),3]); + else w = repmat(Z, [1 1 3]); [Zi, Zj] = find(w > 0); @@ -897,7 +917,7 @@ if numel(unique(mapd)) == 1 % All values are constant constant_value = unique(mapd); - warning('All non-zero, non-NaN values in mapd are constant. Using default colormap range.'); + % warning('All non-zero, non-NaN values in mapd are constant. Using default colormap range.'); cmaprange = [constant_value - 0.1, constant_value + 0.1]; % Default range for constant values return; end diff --git a/CanlabCore/hewma_utility/weighted_reg_old2.m b/CanlabCore/hewma_utility/weighted_reg_old2.m deleted file mode 100644 index 6d008384..00000000 --- a/CanlabCore/hewma_utility/weighted_reg_old2.m +++ /dev/null @@ -1 +0,0 @@ -function [r,xy,v,zp,dfe] = weighted_reg_old2(y,w,varz) % Calculate weighted average using weighted linear least squares % % :Model: % % z_i = 1*zpop + noise % % :Inputs: % % **Y:** % data matrix (nsub x T) % % **w:** % weights % % **varz:** % variance of data at each time point (nsub x T) % % :Outputs: % % **r:** % weighted correlation coeff across columns of y % % **xy:** % weighted covariance matrix % % **v:** % weighted variance estimates for each column of y % % **zp:** % weighted mean of each column of y % [m,n] = size(y); % computation stuff invxwx = inv(X'*W*X); pxw = invxwx * X' * W; W = diag(w); % Weight matrix X = repmat(1,m,1); % Design matrix - 1 column of all ones to calculate average zp = px*y; % weighted population mean e = y - repmat(zp,m,1); % residuals dfe_v = zeros(n,1); R = eye(m) - X*px; % residual inducing matrix % Calculate effective degrees of freedom for i=1:n, V = diag(varz(:,i)); dfe_v(i) = (trace(R*V)^2)/trace(R*V*R*V); % Satherwaite approximation end; dfe = mean(dfe_v); % Calculate average df over all time points. MSE = (e'*W*e)/dfe; % Mean square error xy = inv(X'*W*X)*MSE; % Covariance matrix for zp; xy = 0.5*(xy+xy'); % Remove rounding error v = diag(xy); % Variance for zp r = xy./sqrt(v*v'); % Correlation matrix for zp return \ No newline at end of file diff --git a/CanlabCore/hewma_utility/weighted_reg_oldglmfit_old.m b/CanlabCore/hewma_utility/weighted_reg_oldglmfit_old.m deleted file mode 100644 index 5557d2af..00000000 --- a/CanlabCore/hewma_utility/weighted_reg_oldglmfit_old.m +++ /dev/null @@ -1 +0,0 @@ -function [means,stats] = weighted_reg_oldglmfit_old(Y,varargin) % Calculate weighted average using weighted linear least squares % See examples below for usage % % :Model: % % Y_i = 1*Ypop + noise % % :Inputs: % % **Y:** % data matrix (nsub x T) % % **w:** % weights % % **varY:** % variance of data at each time point (nsub x T) + var between % % :Outputs: % % **Ymean:** % weighted mean of each column of Y % % **dfe:** % error degrees of freedom, adjusted for inequality of variance % (Sattherwaite) and pooled across data columns % % Extended output in stats structure: % % **stats.t:** % t-values for weighted t-test % % **stats.p:** % 2-tailed p-values for weighted t-test % % % **r:** % weighted correlation coeff across columns of Y % % **xy:** % weighted covariance matrix % % **v:** % weighted variance estimates for each column of Y % - sqrt(v) is the standard error of the mean (or grp difference) % % % **stats.fits:** % fits for each group (Ymean by group), low contrast weight group then high % Fastest if no stats are asked for. % % Computation time: % For FULL stats report % - Triples from 500 -> 1000 columns of Y, continues to increase % % For mean/dfe only, fast for full dataset (many columns of Y) % % :Examples: % :: % % % Basic multivariate stats for 1000 columns of dat, no weighting % % Multivariate covariances are meaningful if cols of Y are organized, e.g., timeseries % [means,stats] = weighted_reg(dat(:,1:1000)); % % % The same, but return univariate stats only (good for large Y) % [means,stats] = weighted_reg(dat,'uni'); % % .. % NOTE: TOR CHANGED INPUT TO ASSUME THAT WE SHOULD ENTER VARWI + VARBETWEEN % .. % .. % Set up arguments % .. if nargin == 0, error('Must at least enter data as 1st argument.'); end domultivariate = 1; % multivariate covariance est for Y dobtwn = 0; % between-subjects contrast bcon = []; zpdiff = []; w = []; varY = []; for i = 1:length(varargin) arg = varargin{i}; if ischar(arg) switch lower(arg) case 'w', w = varargin{i+1}; case 'btwn', bcon = contrast_code(varargin{i+1}); case 'vary', varY = varargin{i+1}; case 'uni', domultivariate = 0; end end end [m,n] = size(Y); % fill in missing inputs with default values if ~is_entered(w), w = ones(m,1); end if ~is_entered(varY), varY = ones(m,1); end if is_entered(bcon), dobtwn = 1; end % -------------------------------------- % * Weights and computational steps % -------------------------------------- W = diag(w); % Weight matrix X = repmat(1,m,1); % Design matrix - 1 column of all ones to calculate average % and, separately, use bcon if that's entered invxwx = inv(X'*W*X); hat = invxwx * X'* W; % hat matrix % Between-observations, if entered % ---------------------------------------- if dobtwn invxwx_diff = inv(bcon'*W*bcon); hatdiff = invxwx_diff*bcon'*W; else hatdiff = []; end % -------------------------------------- % * Means and contrast % -------------------------------------- Ymean = hat*Y; % Output: weighted population mean means.Ymean = Ymean; % Between-observation contrast, if entered % ---------------------------------------- if dobtwn zpdiff = hatdiff*Y; % for output; fits for each group; low then high grpfits = repmat(Ymean,2,1) + repmat(sort(unique(bcon)),1,n) .* repmat(zpdiff,2,1); means.grpmeans = grpfits; end if nargout == 1, return, end % -------------------------------------- % * Residuals % -------------------------------------- e = Y - repmat(Ymean,m,1); % residuals if dobtwn % fitted values depending on group fits = repmat(Ymean,m,1) + repmat(bcon,1,n) .* repmat(zpdiff,m,1); ediff = Y - fits; end % -------------------------------------- % * Degrees of freedom % -------------------------------------- [dfe,dfediff] = get_dfe(m,n,X,hat,varY,dobtwn,hatdiff,bcon); if ~domultivariate % ====================================== % % % Univariate stats: MSE, t, and p-values % % % ====================================== % -------------------------------------- % * Mean squared error % -------------------------------------- % Loop version of MSE: avoids out of memory errors for large voxel sets MSE = zeros(1,n); for i=1:n, MSE(i) = e(:,i)'*W*e(:,i); end, MSE = MSE/dfe; v = invxwx * MSE; % variances for mean if dobtwn MSEdiff = zeros(1,n); for i=1:n, MSEdiff(i) = ediff(:,i)'*W*ediff(:,i); end, MSEdiff = MSEdiff/dfediff; vdiff = invxwx_diff * MSEdiff; % variances for mean end % output stats.descrip1 = 'Univariate stats for test against zero:'; stats.v = v; stats.v_descrip = 'V = ste^2; variance of mean estimate'; stats.t = Ymean ./ sqrt(v); stats.p = 2 * ( 1 - tcdf(abs(stats.t),dfe) ); stats.dfe = dfe; if dobtwn stats.descrip2 = 'Univariate stats for between-case contrast:'; stats.bcon = bcon; stats.vdiff = vdiff; stats.tdiff = fits ./ sqrt(v); stats.pdiff = 2 * ( 1 - tcdf(abs(stats.tdiff),dfediff) ); stats.dfediff = dfediff; end else % ====================================== % % % Multivariate stats: MSE, cov(Y), r(Y) % Useful for simulating t-values under dependence % % ====================================== % -------------------------------------- % * Mean squared error % -------------------------------------- % additional output: covariance matrix for Ymean and zdiff across time % (columns) % and correlation matrix for Ymean and zdiff % used in Monte Carlo simulations for controlling false positives % across columns MSE = (e'*W*e)/dfe; % Mean square error if dobtwn MSEdiff = (ediff'*W*ediff)/dfediff; end % -------------------------------------- % * Estimated covariance and correlation % Estimated between-subjects variance (v) % -------------------------------------- xy = invxwx * MSE; % Covariance matrix for Ymean; xy = 0.5*(xy+xy'); % Remove rounding error if dobtwn xydiff = inv(bcon'*W*bcon)*MSEdiff; % Covariance matrix for Ymean; xydiff = 0.5*(xydiff+xydiff'); end v = diag(xy); % Variance for Ymean if dobtwn vdiff = diag(xydiff); end r = xy./sqrt(v*v'); % Correlation matrix for Ymean if dobtwn rdiff = xydiff./sqrt(vdiff*vdiff'); % Correlation matrix for Ymean end stats.descrip1 = 'Multivariate stats for test against zero:'; stats.r = r; stats.v = v; stats.xy = xy; stats.t = Ymean ./ sqrt(v'); stats.p = 2 * ( 1 - tcdf(abs(stats.t),dfe) ); if dobtwn stats.descrip2 = 'Univariate stats for between-case contrast:'; stats.rdiff = rdiff; stats.vdiff = vdiff; stats.xydiff = xydiff; stats.tdiff = fits ./ sqrt(vdiff'); stats.p = 2 * ( 1 - tcdf(abs(stats.tdiff),dfediff) ); end end return function [dfe,dfediff] = get_dfe(m,n,X,hat,varY,dobtwn,hatdiff,bcon) dfediff = []; % Set up residual-forming matrix % -------------------------------------- dfe_v = zeros(n,1); R = eye(m) - X*hat; % residual inducing matrix % contrast, if entered if dobtwn dfe_vdiff = zeros(n,1); Rdiff = eye(m) - bcon * hatdiff; end % Calculate effective degrees of freedom % -------------------------------------- have_unique_vars = size(varY,2) == n; if ~have_unique_vars % Only one (pooled?) vector of variance estimates % -------------------------------------- V = diag(varY(:,1)); dfe = (trace(R*V)^2)/trace(R*V*R*V); % Satherwaite approximation if dobtwn, dfediff = (trace(Rdiff*V)^2)/trace(Rdiff*V*Rdiff*V); end else % Variance estimates for each data vector % -------------------------------------- for i=1:n, % make diagonal matrix of variances V = diag(varY(:,i)); dfe_v(i) = (trace(R*V)^2)/trace(R*V*R*V); % Satherwaite approximation if dobtwn dfe_vdiff(i) = (trace(Rdiff*V)^2)/trace(Rdiff*V*Rdiff*V); end end dfe = mean(dfe_v); % Calculate average df over all columns (pool over data vectors) if dobtwn dfediff = mean(dfe_vdiff); end end return function bool = is_entered(x) bool = exist('x','var') && ~isempty(x); return \ No newline at end of file diff --git a/CanlabCore/poorly_documented_functions.txt b/CanlabCore/poorly_documented_functions.txt new file mode 100644 index 00000000..774c795a --- /dev/null +++ b/CanlabCore/poorly_documented_functions.txt @@ -0,0 +1,788 @@ +# Poorly documented functions in CanlabCore +# +# Files in this list have help blocks that differ substantially from +# the format in CanlabCore/Misc_utilities/documentation_template.m, which +# is the read-the-docs (sphinx) compatible style used elsewhere in the +# toolbox. +# +# Criterion: fewer than 2 of {:Usage:, :Inputs:, :Outputs:} present in the +# first ~120 lines (i.e., "missing most or all of the structured help block"). +# Score format: /3 [+] path +# core = count of {:Usage:, :Inputs:, :Outputs:} present +# extra = count of {:Examples:, :See also:, :Optional Inputs:, :References:} +# (informational; not used for the cutoff) +# +# Excluded directories: External/ OptimizeDesign11/ HRF_Est_Toolbox{2,4}/ +# hewma_utility/ Unit_tests/old_to_integrate/ +# Old_stuff/ (any path containing this segment) +# Excluded files: Misc_utilities/documentation_template.m +# +# Generated: 2026-05-02 +# Surveyed: 1116 .m files; flagged: 762 +# +# Sorted by core score ascending (0/3 first = worst), then by extra count desc, +# then by path. Score 0/3 = no sphinx structure at all; 1/3 = some scaffolding +# present but most fields missing. + +0/3 +3 CanlabCore/@image_vector/apply_mask.m +0/3 +2 CanlabCore/@image_vector/flip.m +0/3 +2 CanlabCore/@image_vector/resample_time.m +0/3 +1 CanlabCore/@fmri_data/runRestMetrics.m +0/3 +1 CanlabCore/@image_vector/montage.m +0/3 +1 CanlabCore/@image_vector/orthviews.m +0/3 +1 CanlabCore/@image_vector/plus.m +0/3 +1 CanlabCore/@image_vector/power.m +0/3 +1 CanlabCore/@image_vector/surface.m +0/3 +1 CanlabCore/@region/region2struct.m +0/3 +1 CanlabCore/Data_processing_tools/selective_average_group.m +0/3 +1 CanlabCore/Index_image_manip_tools/iimg_weighted_ttest.m +0/3 +1 CanlabCore/Misc_utilities/framewise_displacement.m +0/3 +1 CanlabCore/Model_building_tools/onsets2parametric_mod_X.m +0/3 +1 CanlabCore/Parcellation_tools/parcel_cl_nmds_plots.m +0/3 +1 CanlabCore/Statistics_tools/bayes_meta_feature_abstract.m +0/3 +1 CanlabCore/Statistics_tools/moving_average.m +0/3 +1 CanlabCore/Statistics_tools/princomp_largedata.m +0/3 +1 CanlabCore/Visualization_functions/montage_image_Worsley.m +0/3 +1 CanlabCore/Visualization_functions/mvroi_plot_firs.m +0/3 +1 CanlabCore/Visualization_functions/nmdsfig.m +0/3 +1 CanlabCore/Visualization_functions/nmdsfig_fill.m +0/3 +1 CanlabCore/diagnostics/effect_size_map.m +0/3 +1 CanlabCore/diagnostics/scn_component_rsquare.m +0/3 +0 CanlabCore/@atlas/assign_vals.m +0/3 +0 CanlabCore/@atlas/atlas.m +0/3 +0 CanlabCore/@atlas/atlas2region.m +0/3 +0 CanlabCore/@atlas/atlas_add_L_R_to_labels.m +0/3 +0 CanlabCore/@atlas/atlas_similarity.m +0/3 +0 CanlabCore/@atlas/check_properties.m +0/3 +0 CanlabCore/@atlas/downsample_parcellation.m +0/3 +0 CanlabCore/@atlas/get_region_volumes.m +0/3 +0 CanlabCore/@atlas/get_regions_at_crosshairs.m +0/3 +0 CanlabCore/@atlas/horzcat.m +0/3 +0 CanlabCore/@atlas/isosurface.m +0/3 +0 CanlabCore/@atlas/label_table.m +0/3 +0 CanlabCore/@atlas/merge_atlases.m +0/3 +0 CanlabCore/@atlas/num_regions.m +0/3 +0 CanlabCore/@atlas/probability_maps_to_region_index.m +0/3 +0 CanlabCore/@atlas/remove_atlas_region.m +0/3 +0 CanlabCore/@atlas/select_atlas_subset.m +0/3 +0 CanlabCore/@atlas/split_atlas_by_hemisphere.m +0/3 +0 CanlabCore/@atlas/split_atlas_into_contiguous_regions.m +0/3 +0 CanlabCore/@brainpathway/brainpathway.m +0/3 +0 CanlabCore/@brainpathway/brainpathway2fmri_data.m +0/3 +0 CanlabCore/@brainpathway/degree_calc.m +0/3 +0 CanlabCore/@brainpathway/find_node_indices.m +0/3 +0 CanlabCore/@brainpathway/nan2zero.m +0/3 +0 CanlabCore/@brainpathway/private/expand_values_region2voxel.m +0/3 +0 CanlabCore/@brainpathway_multisubject/bct_toolbox_undirected_graph_metrics.m +0/3 +0 CanlabCore/@brainpathway_multisubject/brainpathway_multisubject.m +0/3 +0 CanlabCore/@brainpathway_multisubject/flatten_conn_matrices.m +0/3 +0 CanlabCore/@brainpathway_multisubject/get_wh_subjects.m +0/3 +0 CanlabCore/@brainpathway_multisubject/qcfc.m +0/3 +0 CanlabCore/@brainpathway_multisubject/ttest.m +0/3 +0 CanlabCore/@canlab_dataset/canlab_dataset.m +0/3 +0 CanlabCore/@canlab_dataset/reliability.m +0/3 +0 CanlabCore/@canlab_dataset/replace_values.m +0/3 +0 CanlabCore/@canlab_dataset/select_trials_and_subjects.m +0/3 +0 CanlabCore/@fmri_data/annotate_continuous_neuroimage_maps.m +0/3 +0 CanlabCore/@fmri_data/bootstrap_structure_coeff_diff.m +0/3 +0 CanlabCore/@fmri_data/extract_measures_batch.m +0/3 +0 CanlabCore/@fmri_data/fmri_data.m +0/3 +0 CanlabCore/@fmri_data/get_model_encoding_map.m +0/3 +0 CanlabCore/@fmri_data/model_mpathi.m +0/3 +0 CanlabCore/@fmri_data/neurosynth_lexical_plot.m +0/3 +0 CanlabCore/@fmri_data/normalize_gm_by_wm_csf.m +0/3 +0 CanlabCore/@fmri_data/saveplots.m +0/3 +0 CanlabCore/@fmri_data/spm_coregister.m +0/3 +0 CanlabCore/@fmri_data/structure_coefficient_map.m +0/3 +0 CanlabCore/@fmri_data/structure_coefficients.m +0/3 +0 CanlabCore/@fmri_glm_design_matrix/add.m +0/3 +0 CanlabCore/@fmri_glm_design_matrix/fmri_glm_design_matrix.m +0/3 +0 CanlabCore/@fmri_glm_design_matrix/get_condition_assignments.m +0/3 +0 CanlabCore/@fmri_glm_design_matrix/import_onsets.m +0/3 +0 CanlabCore/@fmri_glm_design_matrix/rotate_to_pca.m +0/3 +0 CanlabCore/@fmri_glm_design_matrix/saveplots.m +0/3 +0 CanlabCore/@fmri_glm_design_matrix/single_trial_estimates.m +0/3 +0 CanlabCore/@fmri_mask_image/fmri_mask_image.m +0/3 +0 CanlabCore/@fmri_timeseries/filloutliers.m +0/3 +0 CanlabCore/@fmri_timeseries/fmri_timeseries.m +0/3 +0 CanlabCore/@fmridisplay/activate_figures.m +0/3 +0 CanlabCore/@fmridisplay/fmridisplay.m +0/3 +0 CanlabCore/@fmridisplay/removeblobs.m +0/3 +0 CanlabCore/@fmridisplay/removepoints.m +0/3 +0 CanlabCore/@fmridisplay/title_montage.m +0/3 +0 CanlabCore/@fmridisplay/zoom_in_on_regions.m +0/3 +0 CanlabCore/@image_vector/enforce_variable_types.m +0/3 +0 CanlabCore/@image_vector/expand_into_atlas_subregions.m +0/3 +0 CanlabCore/@image_vector/get_xyzmm_coordinates.m +0/3 +0 CanlabCore/@image_vector/history.m +0/3 +0 CanlabCore/@image_vector/ica.m +0/3 +0 CanlabCore/@image_vector/image_vector.m +0/3 +0 CanlabCore/@image_vector/isosurface.m +0/3 +0 CanlabCore/@image_vector/mahal.m +0/3 +0 CanlabCore/@image_vector/minus.m +0/3 +0 CanlabCore/@image_vector/pattern_surf_plot_mip.m +0/3 +0 CanlabCore/@image_vector/plot_current_orthviews_coord.m +0/3 +0 CanlabCore/@image_vector/prctile.m +0/3 +0 CanlabCore/@image_vector/read_from_file.m +0/3 +0 CanlabCore/@image_vector/rebuild_volinfo_from_dat.m +0/3 +0 CanlabCore/@image_vector/trim_mask.m +0/3 +0 CanlabCore/@image_vector/unstack_by_condition.m +0/3 +0 CanlabCore/@image_vector/wedge_plot_by_atlas.m +0/3 +0 CanlabCore/@image_vector/winnerTakeAll.m +0/3 +0 CanlabCore/@predictive_model/crossval.m +0/3 +0 CanlabCore/@region/isempty.m +0/3 +0 CanlabCore/@region/isosurface.m +0/3 +0 CanlabCore/@region/labelled_surface.m +0/3 +0 CanlabCore/@region/match_colors_left_right.m +0/3 +0 CanlabCore/@region/orthviews.m +0/3 +0 CanlabCore/@region/region.m +0/3 +0 CanlabCore/@region/region2atlas.m +0/3 +0 CanlabCore/@region/region2fmri_data.m +0/3 +0 CanlabCore/@region/select_coordinates_near_regions.m +0/3 +0 CanlabCore/@region/table_of_atlas_regions_covered.m +0/3 +0 CanlabCore/@region/table_simple.m +0/3 +0 CanlabCore/@statistic_image/check_properties.m +0/3 +0 CanlabCore/@statistic_image/convert2mask.m +0/3 +0 CanlabCore/@statistic_image/statistic_image.m +0/3 +0 CanlabCore/Cifti_plotting/CBIG_registration_fusion_surf2vol_vol2surf/CBIG_RF_projectfsaverage2Vol_single.m +0/3 +0 CanlabCore/Cifti_plotting/CBIG_registration_fusion_surf2vol_vol2surf/standalone_scripts_for_MNI_fsaverage_coordinates_conversion/standalone_scripts_for_MNI_fsaverage_coordinates_conversion/CBIG_RF_MNICoord2fsaverageVertex.m +0/3 +0 CanlabCore/Cifti_plotting/CBIG_registration_fusion_surf2vol_vol2surf/standalone_scripts_for_MNI_fsaverage_coordinates_conversion/standalone_scripts_for_MNI_fsaverage_coordinates_conversion/CBIG_RF_fsaverageVertex2MNICoord.m +0/3 +0 CanlabCore/Cifti_plotting/CBIG_registration_fusion_surf2vol_vol2surf/standalone_scripts_for_MNI_fsaverage_projection/standalone_scripts_for_MNI_fsaverage_projection/CBIG_RF_projectMNI2fsaverage.m +0/3 +0 CanlabCore/Cifti_plotting/read_dlabel_cifti.m +0/3 +0 CanlabCore/Cifti_plotting/surface_outlines.m +0/3 +0 CanlabCore/Cluster_contig_region_tools/Cluster-based_multivar_tools/cluster_FA.m +0/3 +0 CanlabCore/Cluster_contig_region_tools/Cluster-based_multivar_tools/cluster_discrim.m +0/3 +0 CanlabCore/Cluster_contig_region_tools/Cluster-based_multivar_tools/cluster_discrim_montage.m +0/3 +0 CanlabCore/Cluster_contig_region_tools/Cluster-based_multivar_tools/cluster_network.m +0/3 +0 CanlabCore/Cluster_contig_region_tools/Cluster-based_multivar_tools/cluster_nmdsfig.m +0/3 +0 CanlabCore/Cluster_contig_region_tools/Cluster-based_multivar_tools/cluster_nmdsfig_glassbrain.m +0/3 +0 CanlabCore/Cluster_contig_region_tools/Cluster-based_multivar_tools/cluster_princomp.m +0/3 +0 CanlabCore/Cluster_contig_region_tools/Cluster-based_multivar_tools/cluster_princomp2.m +0/3 +0 CanlabCore/Cluster_contig_region_tools/Cluster-based_multivar_tools/discrim_plot.m +0/3 +0 CanlabCore/Cluster_contig_region_tools/Cluster-based_multivar_tools/nmdsfig_tools.m +0/3 +0 CanlabCore/Cluster_contig_region_tools/Cluster-based_multivar_tools/partialcor.m +0/3 +0 CanlabCore/Cluster_contig_region_tools/Cluster_based_statistics/cluster_names.m +0/3 +0 CanlabCore/Cluster_contig_region_tools/Cluster_based_statistics/cluster_sigregions.m +0/3 +0 CanlabCore/Cluster_contig_region_tools/Cluster_based_statistics/cluster_sigregions2.m +0/3 +0 CanlabCore/Cluster_contig_region_tools/Cluster_based_statistics/cluster_ttest.m +0/3 +0 CanlabCore/Cluster_contig_region_tools/Cluster_based_statistics/npm_ttest.m +0/3 +0 CanlabCore/Cluster_contig_region_tools/cluster2region.m +0/3 +0 CanlabCore/Cluster_contig_region_tools/cluster2subclusters.m +0/3 +0 CanlabCore/Cluster_contig_region_tools/cluster_close_enough.m +0/3 +0 CanlabCore/Cluster_contig_region_tools/cluster_table.m +0/3 +0 CanlabCore/Cluster_contig_region_tools/cluster_tool/adjacent_test.m +0/3 +0 CanlabCore/Cluster_contig_region_tools/cluster_tool/check_cl.m +0/3 +0 CanlabCore/Cluster_contig_region_tools/cluster_tool/cl_cl_intersect.m +0/3 +0 CanlabCore/Cluster_contig_region_tools/cluster_tool/cl_img_intersect.m +0/3 +0 CanlabCore/Cluster_contig_region_tools/cluster_tool/cl_subdivide.m +0/3 +0 CanlabCore/Cluster_contig_region_tools/cluster_tool/cluster_tool.m +0/3 +0 CanlabCore/Cluster_contig_region_tools/cluster_tool/cluster_tool_append.m +0/3 +0 CanlabCore/Cluster_contig_region_tools/cluster_tool/cluster_tool_callgui.m +0/3 +0 CanlabCore/Cluster_contig_region_tools/cluster_tool/cluster_tool_draw_sphere.m +0/3 +0 CanlabCore/Cluster_contig_region_tools/cluster_tool/cluster_tool_edit.m +0/3 +0 CanlabCore/Cluster_contig_region_tools/cluster_tool/cluster_tool_getbetas.m +0/3 +0 CanlabCore/Cluster_contig_region_tools/cluster_tool/cluster_tool_getver.m +0/3 +0 CanlabCore/Cluster_contig_region_tools/cluster_tool/cluster_tool_maskq.m +0/3 +0 CanlabCore/Cluster_contig_region_tools/cluster_tool/cluster_tool_masktypeq.m +0/3 +0 CanlabCore/Cluster_contig_region_tools/cluster_tool/cluster_tool_savefunc.m +0/3 +0 CanlabCore/Cluster_contig_region_tools/cluster_tool/cluster_tool_subdivide.m +0/3 +0 CanlabCore/Cluster_contig_region_tools/cluster_tool/cluster_tool_table.m +0/3 +0 CanlabCore/Cluster_contig_region_tools/cluster_tool/cluster_tool_thresh.m +0/3 +0 CanlabCore/Cluster_contig_region_tools/cluster_tool/cluster_tool_tools.m +0/3 +0 CanlabCore/Cluster_contig_region_tools/cluster_tool/cluster_tool_writemasktypeq.m +0/3 +0 CanlabCore/Cluster_contig_region_tools/cluster_tool/mmToVoxel.m +0/3 +0 CanlabCore/Cluster_contig_region_tools/cluster_tool/space_match.m +0/3 +0 CanlabCore/Cluster_contig_region_tools/cluster_tool/sphere_3d.m +0/3 +0 CanlabCore/Cluster_contig_region_tools/cluster_tool/voxelToMm.m +0/3 +0 CanlabCore/Cluster_contig_region_tools/enlarge_cluster.m +0/3 +0 CanlabCore/Data_extraction/DataHash_20190519/DataHash.m +0/3 +0 CanlabCore/Data_extraction/DataHash_20190519/uTest_DataHash.m +0/3 +0 CanlabCore/Data_extraction/Grandfathered/check_timeseries_vals.m +0/3 +0 CanlabCore/Data_extraction/Grandfathered/timeseries4.m +0/3 +0 CanlabCore/Data_extraction/load_atlas.m +0/3 +0 CanlabCore/Data_extraction/read_gzip_nii/icatb_read_gzip_nii.m +0/3 +0 CanlabCore/Data_extraction/sphere_roi_tool_2008.m +0/3 +0 CanlabCore/Data_extraction/sphere_roi_tool_2008_conflict.m +0/3 +0 CanlabCore/Data_processing_tools/canlab_extract_ventricle_wm_timeseries.m +0/3 +0 CanlabCore/Data_processing_tools/canlab_read_excel_sheets.m +0/3 +0 CanlabCore/Data_processing_tools/correlation_2d_to_3d.m +0/3 +0 CanlabCore/Data_processing_tools/correlation_3d_to_2d.m +0/3 +0 CanlabCore/Data_processing_tools/impute_columnwise_mean.m +0/3 +0 CanlabCore/Data_processing_tools/rankdata.m +0/3 +0 CanlabCore/Data_processing_tools/struct2canlab_dataset.m +0/3 +0 CanlabCore/Data_processing_tools/table2canlab_dataset.m +0/3 +0 CanlabCore/Data_processing_tools/table_long2wide.m +0/3 +0 CanlabCore/Filename_tools/canlab_get_underlay_image.m +0/3 +0 CanlabCore/Filename_tools/canlab_list_files.m +0/3 +0 CanlabCore/Filename_tools/canlab_list_subjects.m +0/3 +0 CanlabCore/Filename_tools/extract_bids_info.m +0/3 +0 CanlabCore/Filename_tools/files_to_bids_tbl.m +0/3 +0 CanlabCore/Filename_tools/gunzip_image_names_if_gz.m +0/3 +0 CanlabCore/Filename_tools/rename_lowercase.m +0/3 +0 CanlabCore/Filename_tools/rename_uppercase.m +0/3 +0 CanlabCore/Filename_tools/scn_get_image_names.m +0/3 +0 CanlabCore/GLM_Batch_tools/CANLab_spm_toolbox/spm_cfg_fmri_concatenate.m +0/3 +0 CanlabCore/GLM_Batch_tools/CANLab_spm_toolbox/spm_fmri_concatenate_multisess.m +0/3 +0 CanlabCore/GLM_Batch_tools/CANLab_spm_toolbox/spm_run_fmri_concatenate.m +0/3 +0 CanlabCore/GLM_Batch_tools/canlab_glm_convolve.m +0/3 +0 CanlabCore/GLM_Batch_tools/canlab_glm_getinfo.m +0/3 +0 CanlabCore/GLM_Batch_tools/canlab_glm_group_levels_run1input.m +0/3 +0 CanlabCore/GLM_Batch_tools/canlab_glm_publish_group_levels.m +0/3 +0 CanlabCore/GLM_Batch_tools/canlab_glm_publish_subject_levels.m +0/3 +0 CanlabCore/GLM_Batch_tools/canlab_glm_subject_levels_run1subject.m +0/3 +0 CanlabCore/GLM_Batch_tools/canlab_prep_bidsdir.m +0/3 +0 CanlabCore/GLM_Batch_tools/canlab_spm_glm_extract_trial_info_and_betas.m +0/3 +0 CanlabCore/GLM_Batch_tools/extra/spline_example.m +0/3 +0 CanlabCore/GLM_Batch_tools/promptDSGN.m +0/3 +0 CanlabCore/Image_computation_tools/maskImg.m +0/3 +0 CanlabCore/Image_space_tools/img2voxel.m +0/3 +0 CanlabCore/Image_space_tools/tal2vox.m +0/3 +0 CanlabCore/Index_image_manip_tools/iimg_read_vols.m +0/3 +0 CanlabCore/Index_image_manip_tools/nans2zeros.m +0/3 +0 CanlabCore/Misc_utilities/CERTreader.m +0/3 +0 CanlabCore/Misc_utilities/canlab_defaults.m +0/3 +0 CanlabCore/Misc_utilities/canlab_print_legend_text.m +0/3 +0 CanlabCore/Misc_utilities/cellregexp.m +0/3 +0 CanlabCore/Misc_utilities/check_matlab_version.m +0/3 +0 CanlabCore/Misc_utilities/erase_and_display.m +0/3 +0 CanlabCore/Misc_utilities/erase_string.m +0/3 +0 CanlabCore/Misc_utilities/estimate_time_to_complete.m +0/3 +0 CanlabCore/Misc_utilities/format_text_letters_only.m +0/3 +0 CanlabCore/Misc_utilities/gen_design_files.m +0/3 +0 CanlabCore/Misc_utilities/gen_noise_text_from_R.m +0/3 +0 CanlabCore/Misc_utilities/indic2condf.m +0/3 +0 CanlabCore/Misc_utilities/orthviews_multiple_objs.m +0/3 +0 CanlabCore/Misc_utilities/print_obj_oriented_help.m +0/3 +0 CanlabCore/Misc_utilities/publish_obj_oriented_help.m +0/3 +0 CanlabCore/Misc_utilities/remove_str_from_cell.m +0/3 +0 CanlabCore/Misc_utilities/sec2hms.m +0/3 +0 CanlabCore/Misc_utilities/setFigHeight.m +0/3 +0 CanlabCore/Misc_utilities/setFigWidth.m +0/3 +0 CanlabCore/Misc_utilities/spmify.m +0/3 +0 CanlabCore/Misc_utilities/string2indicator.m +0/3 +0 CanlabCore/Misc_utilities/strip_git_dirs.m +0/3 +0 CanlabCore/Misc_utilities/strip_svn_dirs.m +0/3 +0 CanlabCore/Misc_utilities/subplots.m +0/3 +0 CanlabCore/Model_building_tools/canlab_spm_first_level_spec.m +0/3 +0 CanlabCore/Model_building_tools/comp_model.m +0/3 +0 CanlabCore/Model_building_tools/create_block_design.m +0/3 +0 CanlabCore/Model_building_tools/create_design_single_event.m +0/3 +0 CanlabCore/Model_building_tools/create_random_onsets.m +0/3 +0 CanlabCore/Model_building_tools/design_matrix.m +0/3 +0 CanlabCore/Model_building_tools/hrf_saturation.m +0/3 +0 CanlabCore/Model_building_tools/make_ideal_data.m +0/3 +0 CanlabCore/Model_building_tools/modifiedconv_wager2005.m +0/3 +0 CanlabCore/Parcellation_tools/inconsistent.m +0/3 +0 CanlabCore/ROI_drawing_tools/draw_anatomical_roi.m +0/3 +0 CanlabCore/Reporting/summarize_regression.m +0/3 +0 CanlabCore/Statistics_tools/BCa.m +0/3 +0 CanlabCore/Statistics_tools/Cross_validated_Regression/xval_bestsubsets_brain.m +0/3 +0 CanlabCore/Statistics_tools/Cross_validated_Regression/xval_cross_classfy.m +0/3 +0 CanlabCore/Statistics_tools/Cross_validated_Regression/xval_featureselect_nmdscluster.m +0/3 +0 CanlabCore/Statistics_tools/Cross_validated_Regression/xval_lasso_brain.m +0/3 +0 CanlabCore/Statistics_tools/Cross_validated_Regression/xval_lasso_brain_featureselect.m +0/3 +0 CanlabCore/Statistics_tools/Cross_validated_Regression/xval_lasso_trace.m +0/3 +0 CanlabCore/Statistics_tools/Cross_validated_Regression/xval_regression_bootstrapweightmap_brainplots.m +0/3 +0 CanlabCore/Statistics_tools/Cross_validated_Regression/xval_regression_multisubject.m +0/3 +0 CanlabCore/Statistics_tools/Cross_validated_Regression/xval_regression_multisubject_bootstrapweightmap.m +0/3 +0 CanlabCore/Statistics_tools/Cross_validated_Regression/xval_regression_multisubject_featureselect.m +0/3 +0 CanlabCore/Statistics_tools/Cross_validated_Regression/xval_ridge_brain.m +0/3 +0 CanlabCore/Statistics_tools/Cross_validated_Regression/xval_select_holdout_set.m +0/3 +0 CanlabCore/Statistics_tools/Cross_validated_Regression/xval_select_holdout_set_categoricalcovs.m +0/3 +0 CanlabCore/Statistics_tools/Cross_validated_Regression/xval_simple_ols_featureselect.m +0/3 +0 CanlabCore/Statistics_tools/Cross_validated_Regression/xval_simple_ols_loo.m +0/3 +0 CanlabCore/Statistics_tools/Cross_validated_Regression/xval_simple_ols_loo_featureselect.m +0/3 +0 CanlabCore/Statistics_tools/Iterative_Generalized_Least_Squares/igls.m +0/3 +0 CanlabCore/Statistics_tools/Iterative_Generalized_Least_Squares/igls_brain.m +0/3 +0 CanlabCore/Statistics_tools/Iterative_Generalized_Least_Squares/igls_brain_multilev_wrapper.m +0/3 +0 CanlabCore/Statistics_tools/Iterative_Generalized_Least_Squares/igls_mediation.m +0/3 +0 CanlabCore/Statistics_tools/Iterative_Generalized_Least_Squares/igls_multicond.m +0/3 +0 CanlabCore/Statistics_tools/Iterative_Generalized_Least_Squares/igls_orig.m +0/3 +0 CanlabCore/Statistics_tools/Iterative_Generalized_Least_Squares/igls_plot_slopes.m +0/3 +0 CanlabCore/Statistics_tools/Iterative_Generalized_Least_Squares/igls_sept08.m +0/3 +0 CanlabCore/Statistics_tools/Iterative_Generalized_Least_Squares/igls_sim_fpr.m +0/3 +0 CanlabCore/Statistics_tools/Iterative_Generalized_Least_Squares/igls_simulation_scripts.m +0/3 +0 CanlabCore/Statistics_tools/Iterative_Generalized_Least_Squares/igls_vs_weighted_vs_ols_sim1.m +0/3 +0 CanlabCore/Statistics_tools/RB_empirical_bayes_params.m +0/3 +0 CanlabCore/Statistics_tools/Single_trial_analysis/single_trial_analysis.m +0/3 +0 CanlabCore/Statistics_tools/Single_trial_analysis/single_trial_analysis_tor.m +0/3 +0 CanlabCore/Statistics_tools/Single_trial_analysis/single_trial_analysis_trialmodels.m +0/3 +0 CanlabCore/Statistics_tools/Single_trial_analysis/single_trial_setup_models.m +0/3 +0 CanlabCore/Statistics_tools/Single_trial_analysis/single_trial_weights.m +0/3 +0 CanlabCore/Statistics_tools/Single_trial_analysis/trial_level_beta3.m +0/3 +0 CanlabCore/Statistics_tools/Support_functions/RManova.m +0/3 +0 CanlabCore/Statistics_tools/Support_functions/backprojection.m +0/3 +0 CanlabCore/Statistics_tools/Support_functions/celldat2matrix.m +0/3 +0 CanlabCore/Statistics_tools/Support_functions/contrast2d.m +0/3 +0 CanlabCore/Statistics_tools/Support_functions/contrast3d.m +0/3 +0 CanlabCore/Statistics_tools/Support_functions/convert_to_contour.m +0/3 +0 CanlabCore/Statistics_tools/Support_functions/correlation_to_text.m +0/3 +0 CanlabCore/Statistics_tools/Support_functions/distosim.m +0/3 +0 CanlabCore/Statistics_tools/Support_functions/dyadic.m +0/3 +0 CanlabCore/Statistics_tools/Support_functions/makebinary.m +0/3 +0 CanlabCore/Statistics_tools/Support_functions/othertriangle.m +0/3 +0 CanlabCore/Statistics_tools/Support_functions/pdist1.m +0/3 +0 CanlabCore/Statistics_tools/Support_functions/ranger.m +0/3 +0 CanlabCore/Statistics_tools/Support_functions/shuffles.m +0/3 +0 CanlabCore/Statistics_tools/Support_functions/simtodis.m +0/3 +0 CanlabCore/Statistics_tools/Support_functions/spm2delta.m +0/3 +0 CanlabCore/Statistics_tools/Support_functions/spm2dx.m +0/3 +0 CanlabCore/Statistics_tools/Support_functions/squareform1.m +0/3 +0 CanlabCore/Statistics_tools/Support_functions/stratified_holdout_set.m +0/3 +0 CanlabCore/Statistics_tools/Support_functions/stress.m +0/3 +0 CanlabCore/Statistics_tools/Support_functions/weighted_pdist.m +0/3 +0 CanlabCore/Statistics_tools/Support_functions/xc_stats.m +0/3 +0 CanlabCore/Statistics_tools/arp_V.m +0/3 +0 CanlabCore/Statistics_tools/bct_toolbox_undirected_graph_metrics.m +0/3 +0 CanlabCore/Statistics_tools/cVIF.m +0/3 +0 CanlabCore/Statistics_tools/canlab_fast_euclidean_distance.m +0/3 +0 CanlabCore/Statistics_tools/classify_mds_plot.m +0/3 +0 CanlabCore/Statistics_tools/cohens_d_2sample.m +0/3 +0 CanlabCore/Statistics_tools/corr_within_between.m +0/3 +0 CanlabCore/Statistics_tools/createFit.m +0/3 +0 CanlabCore/Statistics_tools/exactfrsd.m +0/3 +0 CanlabCore/Statistics_tools/exhaustive_map.m +0/3 +0 CanlabCore/Statistics_tools/fracridge.m +0/3 +0 CanlabCore/Statistics_tools/getmeanquality.m +0/3 +0 CanlabCore/Statistics_tools/glm_table.m +0/3 +0 CanlabCore/Statistics_tools/helmertCoding.m +0/3 +0 CanlabCore/Statistics_tools/holm_sidak.m +0/3 +0 CanlabCore/Statistics_tools/intercept.m +0/3 +0 CanlabCore/Statistics_tools/interp1qr.m +0/3 +0 CanlabCore/Statistics_tools/knn_classify_cv_by_profile.m +0/3 +0 CanlabCore/Statistics_tools/lm_multisubject.m +0/3 +0 CanlabCore/Statistics_tools/mediansplit.m +0/3 +0 CanlabCore/Statistics_tools/monotonic_regression.m +0/3 +0 CanlabCore/Statistics_tools/normalize_gm_shift_scale.m +0/3 +0 CanlabCore/Statistics_tools/pairwise_contrasts.m +0/3 +0 CanlabCore/Statistics_tools/partition_variables_indevel.m +0/3 +0 CanlabCore/Statistics_tools/power_calc_find_min_effect_size.m +0/3 +0 CanlabCore/Statistics_tools/regression_table.m +0/3 +0 CanlabCore/Statistics_tools/reliability_estimate_lambda.m +0/3 +0 CanlabCore/Statistics_tools/resid.m +0/3 +0 CanlabCore/Statistics_tools/tiedrankorder.m +0/3 +0 CanlabCore/Statistics_tools/tsquaretest.m +0/3 +0 CanlabCore/Statistics_tools/xval_classify.m +0/3 +0 CanlabCore/Statistics_tools/xval_discriminant_classifier.m +0/3 +0 CanlabCore/Unit_tests/atlas/canlab_test_atlas_constructor.m +0/3 +0 CanlabCore/Unit_tests/canlab_run_all_tests.m +0/3 +0 CanlabCore/Unit_tests/fmri_data/canlab_test_cat_split.m +0/3 +0 CanlabCore/Unit_tests/fmri_data/canlab_test_constructor.m +0/3 +0 CanlabCore/Unit_tests/fmri_data/canlab_test_load_sample.m +0/3 +0 CanlabCore/Unit_tests/fmri_data/canlab_test_regress.m +0/3 +0 CanlabCore/Unit_tests/helpers/canlab_get_sample_fmri_data.m +0/3 +0 CanlabCore/Unit_tests/helpers/canlab_get_sample_thresholded_t.m +0/3 +0 CanlabCore/Unit_tests/image_vector/canlab_test_apply_mask.m +0/3 +0 CanlabCore/Unit_tests/image_vector/canlab_test_display.m +0/3 +0 CanlabCore/Unit_tests/image_vector/canlab_test_extract_roi.m +0/3 +0 CanlabCore/Unit_tests/image_vector/canlab_test_misc.m +0/3 +0 CanlabCore/Unit_tests/image_vector/canlab_test_qc.m +0/3 +0 CanlabCore/Unit_tests/image_vector/canlab_test_replace_remove_empty.m +0/3 +0 CanlabCore/Unit_tests/image_vector/canlab_test_resample_space.m +0/3 +0 CanlabCore/Unit_tests/image_vector/canlab_test_similarity.m +0/3 +0 CanlabCore/Unit_tests/region/canlab_test_region_from_stat.m +0/3 +0 CanlabCore/Unit_tests/statistic_image/canlab_test_table.m +0/3 +0 CanlabCore/Unit_tests/statistic_image/canlab_test_ttest.m +0/3 +0 CanlabCore/Unit_tests/workflows/canlab_test_group_ttest_workflow.m +0/3 +0 CanlabCore/Visualization_functions/3Davi.m +0/3 +0 CanlabCore/Visualization_functions/Support/bar_centers.m +0/3 +0 CanlabCore/Visualization_functions/Support/drawbox.m +0/3 +0 CanlabCore/Visualization_functions/Support/enlarge_axes.m +0/3 +0 CanlabCore/Visualization_functions/Support/equalize_axes.m +0/3 +0 CanlabCore/Visualization_functions/Support/fill_around_line.m +0/3 +0 CanlabCore/Visualization_functions/Support/lightFollowView.m +0/3 +0 CanlabCore/Visualization_functions/Support/lightRestoreSingle.m +0/3 +0 CanlabCore/Visualization_functions/Support/lighthandles.m +0/3 +0 CanlabCore/Visualization_functions/Support/montage_zoom.m +0/3 +0 CanlabCore/Visualization_functions/Support/movie_rotation.m +0/3 +0 CanlabCore/Visualization_functions/Support/movie_tools.m +0/3 +0 CanlabCore/Visualization_functions/Support/plot_onsets.m +0/3 +0 CanlabCore/Visualization_functions/Support/squeeze_axes.m +0/3 +0 CanlabCore/Visualization_functions/Support/steplot.m +0/3 +0 CanlabCore/Visualization_functions/Support/tor_bar_steplot.m +0/3 +0 CanlabCore/Visualization_functions/Support/tor_fig.m +0/3 +0 CanlabCore/Visualization_functions/Support/tor_line_steplot.m +0/3 +0 CanlabCore/Visualization_functions/Support/tor_plot_avgs.m +0/3 +0 CanlabCore/Visualization_functions/active_plus_corr_scatterplot_plugin.m +0/3 +0 CanlabCore/Visualization_functions/add_surface.m +0/3 +0 CanlabCore/Visualization_functions/applycolormap.m +0/3 +0 CanlabCore/Visualization_functions/arrow.m +0/3 +0 CanlabCore/Visualization_functions/brain_movie2.m +0/3 +0 CanlabCore/Visualization_functions/brainstem_slices_3d.m +0/3 +0 CanlabCore/Visualization_functions/bucknerlab_colors.m +0/3 +0 CanlabCore/Visualization_functions/canlab_redblue_symmetric_colormap.m +0/3 +0 CanlabCore/Visualization_functions/colorcube_colors.m +0/3 +0 CanlabCore/Visualization_functions/cprintf.m +0/3 +0 CanlabCore/Visualization_functions/format_strings_for_legend.m +0/3 +0 CanlabCore/Visualization_functions/get_frame.m +0/3 +0 CanlabCore/Visualization_functions/imagePatch.m +0/3 +0 CanlabCore/Visualization_functions/kernelDensitySmoothedHistogram.m +0/3 +0 CanlabCore/Visualization_functions/make3Davi.m +0/3 +0 CanlabCore/Visualization_functions/makeIndSubjectSliceAVI.m +0/3 +0 CanlabCore/Visualization_functions/makeIndSubjectSliceAVI2.m +0/3 +0 CanlabCore/Visualization_functions/makeIndSubjectSliceAVI_uncompressed.m +0/3 +0 CanlabCore/Visualization_functions/make_ind_avi_uncompressed.m +0/3 +0 CanlabCore/Visualization_functions/mdsfig_3d.m +0/3 +0 CanlabCore/Visualization_functions/mea_visualise.m +0/3 +0 CanlabCore/Visualization_functions/mni_TSU.m +0/3 +0 CanlabCore/Visualization_functions/montage_clusters_maxslice.m +0/3 +0 CanlabCore/Visualization_functions/montage_clusters_medial.m +0/3 +0 CanlabCore/Visualization_functions/montage_clusters_text2.m +0/3 +0 CanlabCore/Visualization_functions/movie_stillframes.m +0/3 +0 CanlabCore/Visualization_functions/multi_threshold.m +0/3 +0 CanlabCore/Visualization_functions/multi_threshold2.m +0/3 +0 CanlabCore/Visualization_functions/mvroi_mdsfig_plot2.m +0/3 +0 CanlabCore/Visualization_functions/mvroi_mdsfig_plot_sepstates.m +0/3 +0 CanlabCore/Visualization_functions/mvroi_mdsfig_plugin2.m +0/3 +0 CanlabCore/Visualization_functions/mvroi_residplot_plugin.m +0/3 +0 CanlabCore/Visualization_functions/nmdsfig_legend.m +0/3 +0 CanlabCore/Visualization_functions/nonmonotonic_regression_plot.m +0/3 +0 CanlabCore/Visualization_functions/plot3d.m +0/3 +0 CanlabCore/Visualization_functions/plotMatrixKernelDensity.m +0/3 +0 CanlabCore/Visualization_functions/plotWithinGroup.m +0/3 +0 CanlabCore/Visualization_functions/plot_horizontal_line.m +0/3 +0 CanlabCore/Visualization_functions/plot_parallel_coords.m +0/3 +0 CanlabCore/Visualization_functions/plot_vertical_line.m +0/3 +0 CanlabCore/Visualization_functions/plotbehav.m +0/3 +0 CanlabCore/Visualization_functions/plotclustfir.m +0/3 +0 CanlabCore/Visualization_functions/plothead.m +0/3 +0 CanlabCore/Visualization_functions/renderCluster_ui2.m +0/3 +0 CanlabCore/Visualization_functions/renderCluster_ui3.m +0/3 +0 CanlabCore/Visualization_functions/render_on_cerebellar_flatmap_script.m +0/3 +0 CanlabCore/Visualization_functions/riverplot/riverplot_example_npsplus_bucknerlab.m +0/3 +0 CanlabCore/Visualization_functions/riverplot/riverplot_line.m +0/3 +0 CanlabCore/Visualization_functions/riverplot/riverplot_recolor_layer2.m +0/3 +0 CanlabCore/Visualization_functions/riverplot/riverplot_reorder_matrix.m +0/3 +0 CanlabCore/Visualization_functions/riverplot/riverplot_set_ribbon_property.m +0/3 +0 CanlabCore/Visualization_functions/riverplot/riverplot_toggle_lines.m +0/3 +0 CanlabCore/Visualization_functions/riverplot_remove_ribbons.m +0/3 +0 CanlabCore/Visualization_functions/schloss_colors.m +0/3 +0 CanlabCore/Visualization_functions/seaborn_colors.m +0/3 +0 CanlabCore/Visualization_functions/showme.m +0/3 +0 CanlabCore/Visualization_functions/spm_figure_canlab.m +0/3 +0 CanlabCore/Visualization_functions/spm_ov_black2white.m +0/3 +0 CanlabCore/Visualization_functions/standardMRIhead.m +0/3 +0 CanlabCore/Visualization_functions/talairach_cluster_plugin.m +0/3 +0 CanlabCore/Visualization_functions/temp_save_slices.m +0/3 +0 CanlabCore/Visualization_functions/testclust_plot_plugin.m +0/3 +0 CanlabCore/Visualization_functions/tor_3d_head.m +0/3 +0 CanlabCore/Visualization_functions/values_to_colors.m +0/3 +0 CanlabCore/Visualization_functions/wordcloud_stat.m +0/3 +0 CanlabCore/Visualization_functions/xval_lasso_brain_permutation_histogram.m +0/3 +0 CanlabCore/canlab_canonical_brains/Canonical_brains_surfaces/create_anatomical_underlay_2016.m +0/3 +0 CanlabCore/canlab_canonical_brains/Canonical_brains_surfaces/src/create_MNI152NLin2009cAsym_to_surf_nearest_neighbor_projections.m +0/3 +0 CanlabCore/canlab_canonical_brains/Canonical_brains_surfaces/src/create_MNI152NLin2009cAsym_to_surf_projections.m +0/3 +0 CanlabCore/canlab_canonical_brains/Canonical_brains_surfaces/src/create_MNI152NLin6Asym_to_surf_nearest_neighbor_projections.m +0/3 +0 CanlabCore/canlab_canonical_brains/Canonical_brains_surfaces/src/create_MNI152NLin6Asym_to_surf_projections.m +0/3 +0 CanlabCore/canlab_canonical_brains/Canonical_brains_surfaces/src/create_colin27_to_surf_nearest_neighbor_projections.m +0/3 +0 CanlabCore/canlab_canonical_brains/Canonical_brains_surfaces/src/create_colin27_to_surf_projections.m +0/3 +0 CanlabCore/canlab_canonical_brains/Canonical_brains_surfaces/src/private/create_canlab_surf_file.m +0/3 +0 CanlabCore/canlab_canonical_brains/Canonical_brains_surfaces/src/private/hotcool_split_colormap.m +0/3 +0 CanlabCore/canlab_canonical_brains/Canonical_brains_surfaces/src/private/map_to_colors.m +0/3 +0 CanlabCore/canlab_canonical_brains/Canonical_brains_surfaces/src/spherical_icosahedral_interpolation.m +0/3 +0 CanlabCore/canlab_canonical_brains/Combined_multiatlas_ROI_masks/add_new_anat_atlas_script.m +0/3 +0 CanlabCore/canlab_canonical_brains/Combined_multiatlas_ROI_masks/atlas_labels_combined_info.m +0/3 +0 CanlabCore/canlab_canonical_brains/Combined_multiatlas_ROI_masks/atlas_spmanatomy_get_cluster.m +0/3 +0 CanlabCore/canlab_canonical_brains/Cortical_ROI_masks/insula_ribbon_partial_script.m +0/3 +0 CanlabCore/canlab_canonical_brains/Cortical_ROI_masks/prep_cortical_masks.m +0/3 +0 CanlabCore/canlab_canonical_brains/Thalamus_brainstem_ROIs_surfaces/draw_coordinate_based_brainstem_rois_2018.m +0/3 +0 CanlabCore/canlab_canonical_brains/Thalamus_brainstem_ROIs_surfaces/draw_dorsal_raphe_rois_2018.m +0/3 +0 CanlabCore/canlab_canonical_brains/Thalamus_brainstem_ROIs_surfaces/draw_pag_roi_2018.m +0/3 +0 CanlabCore/canlab_canonical_brains/Thalamus_brainstem_ROIs_surfaces/draw_sup_inf_colliculus_roi_2018.m +0/3 +0 CanlabCore/canlab_canonical_brains/Thalamus_brainstem_ROIs_surfaces/make_brainstem_2018.m +0/3 +0 CanlabCore/canlab_canonical_brains/add_text_label.m +0/3 +0 CanlabCore/canlab_toolbox_setup.m +0/3 +0 CanlabCore/diagnostics/Mis_modeling/mismodel_diagnostics_brain.m +0/3 +0 CanlabCore/diagnostics/Mis_modeling/write_residual_images.m +0/3 +0 CanlabCore/diagnostics/ScanSim4.m +0/3 +0 CanlabCore/diagnostics/power_loss.m +0/3 +0 CanlabCore/diagnostics/reset_SPMcfg.m +0/3 +0 CanlabCore/diagnostics/reslice_seg_anatomy.m +0/3 +0 CanlabCore/diagnostics/saccade_fix_nuisance.m +0/3 +0 CanlabCore/diagnostics/scn_check_vmpfc_signal.m +0/3 +0 CanlabCore/diagnostics/scnlab_pca_denoise_session.m +0/3 +0 CanlabCore/diagnostics/view_betas.m +0/3 +0 CanlabCore/fmridisplay_helper_functions/lighten_underlay_edges.m +0/3 +0 CanlabCore/fmridisplay_helper_functions/removepoints.m +0/3 +0 CanlabCore/mlpcr/cv_mlpcr.m +0/3 +0 CanlabCore/mlpcr/cv_mlpcr3.m +0/3 +0 CanlabCore/mlpcr/cv_mlpcr3_bt.m +0/3 +0 CanlabCore/mlpcr/cv_mlpcr3_wi.m +0/3 +0 CanlabCore/mlpcr/cv_mlpcr_bt.m +0/3 +0 CanlabCore/mlpcr/cv_mlpcr_wi.m +0/3 +0 CanlabCore/mlpcr/mlpcr2.m +0/3 +0 CanlabCore/mlpcr/mlpcr3.m +0/3 +0 CanlabCore/web_repository_tools/retrieve_neurovault_collection.m +1/3 +3 CanlabCore/@image_vector/apply_atlas.m +1/3 +3 CanlabCore/Image_computation_tools/spm_htw_from_fit.m +1/3 +2 CanlabCore/@fmridisplay/addpoints.m +1/3 +2 CanlabCore/@image_vector/mean.m +1/3 +2 CanlabCore/@image_vector/slices.m +1/3 +2 CanlabCore/Data_extraction/extract_raw_data.m +1/3 +2 CanlabCore/Data_processing_tools/splinetrim.m +1/3 +2 CanlabCore/Image_computation_tools/apply_derivative_boost.m +1/3 +2 CanlabCore/Image_computation_tools/mask_image.m +1/3 +2 CanlabCore/Image_space_tools/scn_map_image.m +1/3 +2 CanlabCore/Image_space_tools/scn_resample_voxel_size.m +1/3 +2 CanlabCore/Misc_utilities/implode.m +1/3 +2 CanlabCore/Parcellation_tools/parcel_complete_sets.m +1/3 +2 CanlabCore/Statistics_tools/matrix_direct_effects_ridge.m +1/3 +2 CanlabCore/Visualization_functions/surface_cutaway.m +1/3 +2 CanlabCore/diagnostics/canlab_qc_metrics1.m +1/3 +2 CanlabCore/diagnostics/scn_spm_design_check.m +1/3 +1 CanlabCore/@atlas/montage.m +1/3 +1 CanlabCore/@fmri_data/hrf_fit.m +1/3 +1 CanlabCore/@fmri_glm_design_matrix/replace_basis_set.m +1/3 +1 CanlabCore/@image_vector/horzcat.m +1/3 +1 CanlabCore/@image_vector/preprocess.m +1/3 +1 CanlabCore/@image_vector/remove_empty.m +1/3 +1 CanlabCore/@image_vector/render_on_cerebellar_flatmap.m +1/3 +1 CanlabCore/@image_vector/replace_empty.m +1/3 +1 CanlabCore/@image_vector/resample_space.m +1/3 +1 CanlabCore/@image_vector/rmssd_movie.m +1/3 +1 CanlabCore/@image_vector/slice_movie.m +1/3 +1 CanlabCore/@region/montage.m +1/3 +1 CanlabCore/@statistic_image/orthviews.m +1/3 +1 CanlabCore/Cifti_plotting/cifti_struct_2_region_obj.m +1/3 +1 CanlabCore/Cluster_contig_region_tools/clusters2mask.m +1/3 +1 CanlabCore/Cluster_contig_region_tools/mask2clusters.m +1/3 +1 CanlabCore/Data_processing_tools/scnlab_filter_fmri_data.m +1/3 +1 CanlabCore/Filename_tools/dicom_tarzip.m +1/3 +1 CanlabCore/GLM_Batch_tools/canlab_glm_group_levels.m +1/3 +1 CanlabCore/Image_computation_tools/image_eval_function_multisubj.m +1/3 +1 CanlabCore/Image_computation_tools/mask_create_from_image_set.m +1/3 +1 CanlabCore/Image_computation_tools/reslice_imgs.m +1/3 +1 CanlabCore/Index_image_manip_tools/iimg_read_img.m +1/3 +1 CanlabCore/Index_image_manip_tools/iimg_reconstruct_3dvol.m +1/3 +1 CanlabCore/Index_image_manip_tools/iimg_reconstruct_vols.m +1/3 +1 CanlabCore/Misc_utilities/get_first_help_lines.m +1/3 +1 CanlabCore/Misc_utilities/naninsert.m +1/3 +1 CanlabCore/Misc_utilities/oneinsert.m +1/3 +1 CanlabCore/Misc_utilities/parse_char_to_cell.m +1/3 +1 CanlabCore/Misc_utilities/print_matrix.m +1/3 +1 CanlabCore/Misc_utilities/read_edat_output_2008.m +1/3 +1 CanlabCore/Misc_utilities/search_struct_fields.m +1/3 +1 CanlabCore/Model_building_tools/intercept_model.m +1/3 +1 CanlabCore/Parcellation_tools/parcel_cl_nmds.m +1/3 +1 CanlabCore/Statistics_tools/F_test_full_vs_red.m +1/3 +1 CanlabCore/Statistics_tools/F_test_no_intercept.m +1/3 +1 CanlabCore/Statistics_tools/barplot_get_within_ste.m +1/3 +1 CanlabCore/Statistics_tools/classify_naive_bayes_objfun.m +1/3 +1 CanlabCore/Statistics_tools/classify_viz_regions.m +1/3 +1 CanlabCore/Statistics_tools/correl_compare_dep.m +1/3 +1 CanlabCore/Statistics_tools/correl_compare_dep_permtest.m +1/3 +1 CanlabCore/Statistics_tools/correl_compare_dep_search.m +1/3 +1 CanlabCore/Statistics_tools/fit_gls_brain.m +1/3 +1 CanlabCore/Statistics_tools/nonlin_param_modulator.m +1/3 +1 CanlabCore/Statistics_tools/repeated_ancova.m +1/3 +1 CanlabCore/Statistics_tools/roc_boot.m +1/3 +1 CanlabCore/Statistics_tools/t_test2.m +1/3 +1 CanlabCore/Visualization_functions/barplot_columns.m +1/3 +1 CanlabCore/Visualization_functions/barplot_columns2.m +1/3 +1 CanlabCore/Visualization_functions/barplot_columns3.m +1/3 +1 CanlabCore/Visualization_functions/canlab_results_fmridisplay.m +1/3 +1 CanlabCore/Visualization_functions/cluster_orthviews_montage.m +1/3 +1 CanlabCore/Visualization_functions/cluster_surf_batch.m +1/3 +1 CanlabCore/Visualization_functions/cluster_surf_batch2.m +1/3 +1 CanlabCore/Visualization_functions/compare_filtered_t.m +1/3 +1 CanlabCore/Visualization_functions/errorbar_horizontal.m +1/3 +1 CanlabCore/Visualization_functions/make_figure_into_orthviews.m +1/3 +1 CanlabCore/Visualization_functions/makelegend.m +1/3 +1 CanlabCore/Visualization_functions/map_data_to_colormap.m +1/3 +1 CanlabCore/Visualization_functions/mdsfig.m +1/3 +1 CanlabCore/Visualization_functions/movie_of_slice_timeseries.m +1/3 +1 CanlabCore/Visualization_functions/nmdsfig1D.m +1/3 +1 CanlabCore/Visualization_functions/plot_error.m +1/3 +1 CanlabCore/Visualization_functions/plot_matrix_cols.m +1/3 +1 CanlabCore/Visualization_functions/spm_orthviews_change_colormap.m +1/3 +1 CanlabCore/Visualization_functions/surf_plot_tor.m +1/3 +1 CanlabCore/Visualization_functions/talairach_clusters.m +1/3 +1 CanlabCore/Visualization_functions/tor_fill_steplot.m +1/3 +1 CanlabCore/diagnostics/batch_t_histograms.m +1/3 +1 CanlabCore/diagnostics/img_hist.m +1/3 +1 CanlabCore/diagnostics/img_hist2.m +1/3 +1 CanlabCore/diagnostics/publish_scn_session_spike_id.m +1/3 +1 CanlabCore/diagnostics/sanity_check_fMRI_timemod.m +1/3 +1 CanlabCore/peak_coordinates/image2coordinates.m +1/3 +0 CanlabCore/@atlas/create.m +1/3 +0 CanlabCore/@fmri_data/create.m +1/3 +0 CanlabCore/@fmri_data/fitlme_voxelwise.m +1/3 +0 CanlabCore/@fmri_data/horzcat.m +1/3 +0 CanlabCore/@fmri_data/validate_object.m +1/3 +0 CanlabCore/@fmri_data/windsorize.m +1/3 +0 CanlabCore/@fmri_glm_design_matrix/Add_Event_Info.m +1/3 +0 CanlabCore/@fmri_glm_design_matrix/build.m +1/3 +0 CanlabCore/@fmri_glm_design_matrix/get_session_X.m +1/3 +0 CanlabCore/@fmri_glm_design_matrix/plot.m +1/3 +0 CanlabCore/@fmri_glm_design_matrix/robustfit.m +1/3 +0 CanlabCore/@fmridisplay/legend.m +1/3 +0 CanlabCore/@fmridisplay/transparency_change.m +1/3 +0 CanlabCore/@image_vector/check_image_filenames.m +1/3 +0 CanlabCore/@image_vector/compare_space.m +1/3 +0 CanlabCore/@image_vector/image_similarity_plot.m +1/3 +0 CanlabCore/@image_vector/interpolate.m +1/3 +0 CanlabCore/@image_vector/reconstruct_image.m +1/3 +0 CanlabCore/@image_vector/reparse_contiguous.m +1/3 +0 CanlabCore/@image_vector/union.m +1/3 +0 CanlabCore/@predictive_model/predictive_model.m +1/3 +0 CanlabCore/@region/check_extracted_data.m +1/3 +0 CanlabCore/@region/merge.m +1/3 +0 CanlabCore/@region/posneg_separate.m +1/3 +0 CanlabCore/@region/region2imagevec.m +1/3 +0 CanlabCore/@region/reparse_continguous.m +1/3 +0 CanlabCore/@statistic_image/conjunction.m +1/3 +0 CanlabCore/@statistic_image/estimateBayesFactor.m +1/3 +0 CanlabCore/@statistic_image/reparse_contiguous.m +1/3 +0 CanlabCore/@statistic_image/select_one_image.m +1/3 +0 CanlabCore/Cifti_plotting/make_surface_figure.m +1/3 +0 CanlabCore/Cluster_contig_region_tools/anat_subclusters.m +1/3 +0 CanlabCore/Cluster_contig_region_tools/cluster_export_pngs.m +1/3 +0 CanlabCore/Cluster_contig_region_tools/cluster_find_index.m +1/3 +0 CanlabCore/Cluster_contig_region_tools/cluster_interp.m +1/3 +0 CanlabCore/Cluster_contig_region_tools/cluster_intersection.m +1/3 +0 CanlabCore/Cluster_contig_region_tools/cluster_local_maxima.m +1/3 +0 CanlabCore/Cluster_contig_region_tools/cluster_set_intersection.m +1/3 +0 CanlabCore/Cluster_contig_region_tools/cluster_table_successive_threshold.m +1/3 +0 CanlabCore/Cluster_contig_region_tools/clusters2CLU.m +1/3 +0 CanlabCore/Cluster_contig_region_tools/image2clusters.m +1/3 +0 CanlabCore/Cluster_contig_region_tools/merge_clusters.m +1/3 +0 CanlabCore/Cluster_contig_region_tools/merge_nearby_clusters.m +1/3 +0 CanlabCore/Cluster_contig_region_tools/subclusters_from_local_max.m +1/3 +0 CanlabCore/Cluster_contig_region_tools/xyz2clusters.m +1/3 +0 CanlabCore/Data_extraction/canlab_load_ROI.m +1/3 +0 CanlabCore/Data_extraction/load_image_set.m +1/3 +0 CanlabCore/Data_extraction/read_hdr.m +1/3 +0 CanlabCore/Data_extraction/timeseries_extract_slice.m +1/3 +0 CanlabCore/Data_processing_tools/center_of_mass.m +1/3 +0 CanlabCore/Data_processing_tools/detransition.m +1/3 +0 CanlabCore/Data_processing_tools/filterAdjust.m +1/3 +0 CanlabCore/Data_processing_tools/get_snr.m +1/3 +0 CanlabCore/Data_processing_tools/luisFilter.m +1/3 +0 CanlabCore/Data_processing_tools/nuisance_cov_estimates.m +1/3 +0 CanlabCore/Data_processing_tools/resample_scnlab.m +1/3 +0 CanlabCore/Data_processing_tools/scale.m +1/3 +0 CanlabCore/Data_processing_tools/scnlab_outlier_id.m +1/3 +0 CanlabCore/Data_processing_tools/smooth_timeseries.m +1/3 +0 CanlabCore/Data_processing_tools/trimts.m +1/3 +0 CanlabCore/Filename_tools/check_valid_imagename.m +1/3 +0 CanlabCore/Filename_tools/copy_image_files.m +1/3 +0 CanlabCore/Filename_tools/delete_ana_imgs.m +1/3 +0 CanlabCore/Filename_tools/escapeForShell.m +1/3 +0 CanlabCore/Filename_tools/expand_4d_filenames.m +1/3 +0 CanlabCore/Filename_tools/getfullpath.m +1/3 +0 CanlabCore/Filename_tools/nums_from_text.m +1/3 +0 CanlabCore/Filename_tools/scan_get_files.m +1/3 +0 CanlabCore/Filename_tools/sort_image_filenames.m +1/3 +0 CanlabCore/GLM_Batch_tools/canlab_glm_maskstats.m +1/3 +0 CanlabCore/GLM_Batch_tools/canlab_glm_publish.m +1/3 +0 CanlabCore/GLM_Batch_tools/canlab_glm_roistats.m +1/3 +0 CanlabCore/GLM_Batch_tools/canlab_glm_subject_levels.m +1/3 +0 CanlabCore/Image_computation_tools/image_histogram1d.m +1/3 +0 CanlabCore/Image_computation_tools/mask_intersection.m +1/3 +0 CanlabCore/Image_computation_tools/mask_intersection2.m +1/3 +0 CanlabCore/Image_computation_tools/mask_union.m +1/3 +0 CanlabCore/Image_computation_tools/percent_sig_image.m +1/3 +0 CanlabCore/Image_computation_tools/reverse_mask.m +1/3 +0 CanlabCore/Image_computation_tools/scn_num_volumes.m +1/3 +0 CanlabCore/Image_computation_tools/tor_spm_mean_ui.m +1/3 +0 CanlabCore/Image_space_tools/check_spm_mat.m +1/3 +0 CanlabCore/Image_space_tools/check_spm_matfiles.m +1/3 +0 CanlabCore/Image_space_tools/mask2voxel.m +1/3 +0 CanlabCore/Image_space_tools/mni2tal.m +1/3 +0 CanlabCore/Image_space_tools/tal2mni.m +1/3 +0 CanlabCore/Image_space_tools/transform_coordinates.m +1/3 +0 CanlabCore/Image_space_tools/voxel2mask.m +1/3 +0 CanlabCore/Image_thresholding/cl_ext_spm_spm.m +1/3 +0 CanlabCore/Image_thresholding/clusterSizeMask.m +1/3 +0 CanlabCore/Index_image_manip_tools/iimg_cluster_prune.m +1/3 +0 CanlabCore/Index_image_manip_tools/iimg_indx2clusters.m +1/3 +0 CanlabCore/Index_image_manip_tools/iimg_indx2contiguousxyz.m +1/3 +0 CanlabCore/Index_image_manip_tools/iimg_intersection.m +1/3 +0 CanlabCore/Index_image_manip_tools/iimg_make_sure_indx.m +1/3 +0 CanlabCore/Index_image_manip_tools/iimg_mask.m +1/3 +0 CanlabCore/Index_image_manip_tools/iimg_princomp.m +1/3 +0 CanlabCore/Index_image_manip_tools/iimg_princomp_display.m +1/3 +0 CanlabCore/Index_image_manip_tools/iimg_reslice.m +1/3 +0 CanlabCore/Index_image_manip_tools/iimg_xyz2indx.m +1/3 +0 CanlabCore/Misc_utilities/blank_struct.m +1/3 +0 CanlabCore/Misc_utilities/canlab_pad.m +1/3 +0 CanlabCore/Misc_utilities/circle.m +1/3 +0 CanlabCore/Misc_utilities/combine_structs.m +1/3 +0 CanlabCore/Misc_utilities/getRandom.m +1/3 +0 CanlabCore/Misc_utilities/nanremove.m +1/3 +0 CanlabCore/Misc_utilities/padwithnan.m +1/3 +0 CanlabCore/Misc_utilities/parse_edat_txt.m +1/3 +0 CanlabCore/Misc_utilities/robustcsvread.m +1/3 +0 CanlabCore/Misc_utilities/scn_get_datetime.m +1/3 +0 CanlabCore/Misc_utilities/scn_mat_conform.m +1/3 +0 CanlabCore/Misc_utilities/strip_path_dirs.m +1/3 +0 CanlabCore/Misc_utilities/strrep_recurse.m +1/3 +0 CanlabCore/Misc_utilities/struct_strrep.m +1/3 +0 CanlabCore/Misc_utilities/zeroinsert.m +1/3 +0 CanlabCore/Model_building_tools/plot_ideal_deconv5.m +1/3 +0 CanlabCore/Model_building_tools/spm_mat2batchinput.m +1/3 +0 CanlabCore/Parcellation_tools/mask_princomp.m +1/3 +0 CanlabCore/Parcellation_tools/parcel_clusters.m +1/3 +0 CanlabCore/ROI_drawing_tools/clusters2roimask.m +1/3 +0 CanlabCore/Statistics_tools/Bspline.m +1/3 +0 CanlabCore/Statistics_tools/contrast_code.m +1/3 +0 CanlabCore/Statistics_tools/create_orthogonal_contrast_set.m +1/3 +0 CanlabCore/Statistics_tools/dice_coeff_image.m +1/3 +0 CanlabCore/Statistics_tools/fisherz.m +1/3 +0 CanlabCore/Statistics_tools/loess_multilevel.m +1/3 +0 CanlabCore/Statistics_tools/matrix_direct_effects.m +1/3 +0 CanlabCore/Statistics_tools/matrix_eval_function.m +1/3 +0 CanlabCore/Statistics_tools/noise_arp.m +1/3 +0 CanlabCore/Statistics_tools/permute_setupperms.m +1/3 +0 CanlabCore/Statistics_tools/prplot_multilevel.m +1/3 +0 CanlabCore/Statistics_tools/robust_reg_pooled.m +1/3 +0 CanlabCore/Statistics_tools/rsquare_calc.m +1/3 +0 CanlabCore/Statistics_tools/scn_stats_helper_functions.m +1/3 +0 CanlabCore/Statistics_tools/searchlight_disti.m +1/3 +0 CanlabCore/Statistics_tools/shift_signal.m +1/3 +0 CanlabCore/Statistics_tools/signtest_matrix.m +1/3 +0 CanlabCore/Statistics_tools/ste.m +1/3 +0 CanlabCore/Statistics_tools/stepwise_tor.m +1/3 +0 CanlabCore/Statistics_tools/subset_indicator_matrix.m +1/3 +0 CanlabCore/Statistics_tools/svm_rfe.m +1/3 +0 CanlabCore/Statistics_tools/xcorr_xy_multisubject.m +1/3 +0 CanlabCore/Visualization_functions/addbrain.m +1/3 +0 CanlabCore/Visualization_functions/barplot_colored.m +1/3 +0 CanlabCore/Visualization_functions/barplotter.m +1/3 +0 CanlabCore/Visualization_functions/cl_line_plots.m +1/3 +0 CanlabCore/Visualization_functions/cl_overlap.m +1/3 +0 CanlabCore/Visualization_functions/close_non_spm_graphics_figures.m +1/3 +0 CanlabCore/Visualization_functions/cluster_orthviews_overlap2.m +1/3 +0 CanlabCore/Visualization_functions/cluster_orthviews_showcenters.m +1/3 +0 CanlabCore/Visualization_functions/create_figure.m +1/3 +0 CanlabCore/Visualization_functions/fill_area_around_points.m +1/3 +0 CanlabCore/Visualization_functions/get_cluster_volume.m +1/3 +0 CanlabCore/Visualization_functions/glassbrain_avi.m +1/3 +0 CanlabCore/Visualization_functions/make3Davi_uncompressed.m +1/3 +0 CanlabCore/Visualization_functions/mask2surface.m +1/3 +0 CanlabCore/Visualization_functions/montage_clusters.m +1/3 +0 CanlabCore/Visualization_functions/montage_clusters_points.m +1/3 +0 CanlabCore/Visualization_functions/montage_clusters_text.m +1/3 +0 CanlabCore/Visualization_functions/plot_ellipse.m +1/3 +0 CanlabCore/Visualization_functions/plot_hrf_model_fit.m +1/3 +0 CanlabCore/Visualization_functions/renderCluster_ui.m +1/3 +0 CanlabCore/Visualization_functions/scatter_glm.m +1/3 +0 CanlabCore/Visualization_functions/scn_export_papersetup.m +1/3 +0 CanlabCore/Visualization_functions/scn_export_spm_window.m +1/3 +0 CanlabCore/Visualization_functions/scn_standard_colors.m +1/3 +0 CanlabCore/Visualization_functions/spm_orthviews_name_axis.m +1/3 +0 CanlabCore/Visualization_functions/spm_orthviews_showposition.m +1/3 +0 CanlabCore/Visualization_functions/spm_orthviews_white_background.m +1/3 +0 CanlabCore/Visualization_functions/tor_ihb_GetClusterSet.m +1/3 +0 CanlabCore/Visualization_functions/tor_ihb_GetClusters.m +1/3 +0 CanlabCore/Visualization_functions/tor_ihb_TalSpace.m +1/3 +0 CanlabCore/diagnostics/check_cluster_data.m +1/3 +0 CanlabCore/diagnostics/displayme.m +1/3 +0 CanlabCore/diagnostics/fft_calc.m +1/3 +0 CanlabCore/diagnostics/hist2.m +1/3 +0 CanlabCore/diagnostics/orthogonalize.m +1/3 +0 CanlabCore/diagnostics/scale_imgs_by_csf.m +1/3 +0 CanlabCore/diagnostics/scn_spm_choose_hpfilter.m +1/3 +0 CanlabCore/diagnostics/scn_spm_get_events_of_interest.m +1/3 +0 CanlabCore/fmridisplay_helper_functions/clusters2mask2011.m +1/3 +0 CanlabCore/fmridisplay_helper_functions/display_slice.m diff --git a/README.md b/README.md index 2cf20c42..35a4dbe6 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,15 @@ CanlabCore ========== +[![tests](https://github.com/canlab/CanlabCore/actions/workflows/test.yml/badge.svg)](https://github.com/canlab/CanlabCore/actions/workflows/test.yml) +[![tests-walkthroughs](https://github.com/canlab/CanlabCore/actions/workflows/tests-walkthroughs.yml/badge.svg)](https://github.com/canlab/CanlabCore/actions/workflows/tests-walkthroughs.yml) + This repository contains core tools for MRI/fMRI/PET analysis from the Cognitive and Affective Neuorscience Lab (Tor Wager, PI) and our collaborators. Many of these functions are needed to run other toolboxes, e.g., the CAN lab’s multilevel mediation and Martin Lindquist’s hemodynamic response estimation toolboxes. A brief introduction to the toolbox can be found here. The tools include object-oriented tools for doing neuroimaging analysis with simple commands and scripts that provide high-level functionality for neuroimaging analysis. For example, there is an "fmri_data" object type that contains neuroimaging datasets (both PET and fMRI data are ok, despite the name). If you have created and object called my_fmri_data_obj, then plot(my_fmri_data_obj) will generate a series of plots specific to neuroimaging data, including an interactive brain viewer (courtesy of SPM software). predict(my_fmri_data_obj) will perform cross-validated multivariate prediction of outcomes based on brain data. ica(my_fmri_data_obj) will perform independent components analysis on the data, and so forth. +📖 **[Object methods reference →](docs/Object_methods.md)** — class-by-class index of every method, with runnable examples and sample figures. The fastest way to learn the API. + The repository also includes other useful toolboxes, including: - fMRI design optimization using a genetic algorithm (OptimizeGA) - fMRI hemodynamic response function estimation (HRF_Est_Toolbox2) @@ -16,8 +21,9 @@ Getting help and additional information: ------------------------------------------------------------ Sources of documentation for this toolbox: -1. For function-by-function help documents on the Core Tools objects and functions, see the help pages on Readthedocs. -2. For brief, documented code examples of some specific functions, and a batch script system that uses the CanlabCore object-oriented tools for second-level neuroimaging analysis, see CANlab_help_examples github repository +1. **[Object methods reference](docs/Object_methods.md)** in this repository — class-by-class index (`fmri_data`, `image_vector`, `statistic_image`, `atlas`, `region`, ...) with per-method pages, runnable examples, sample figures, and per-function code maps. +2. For function-by-function help documents on the Core Tools objects and functions, see the help pages on Readthedocs. +3. For brief, documented code examples of some specific functions, and a batch script system that uses the CanlabCore object-oriented tools for second-level neuroimaging analysis, see CANlab_help_examples github repository For more information on fMRI analysis generally, see Martin and Tor's online book and our free Coursera videos and classes Principles of fMRI Part 1 and Part 2 . diff --git a/docs/.gitignore b/docs/.gitignore deleted file mode 100755 index bd5d1888..00000000 --- a/docs/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -_site/ - diff --git a/docs/.idea/lslipski.github.io.iml b/docs/.idea/lslipski.github.io.iml deleted file mode 100755 index 67116063..00000000 --- a/docs/.idea/lslipski.github.io.iml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/docs/.idea/misc.xml b/docs/.idea/misc.xml deleted file mode 100755 index 8d982c7f..00000000 --- a/docs/.idea/misc.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/docs/.idea/modules.xml b/docs/.idea/modules.xml deleted file mode 100755 index 0a19aff0..00000000 --- a/docs/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/docs/.idea/vcs.xml b/docs/.idea/vcs.xml deleted file mode 100755 index 94a25f7f..00000000 --- a/docs/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/docs/.idea/workspace.xml b/docs/.idea/workspace.xml deleted file mode 100755 index 35748165..00000000 --- a/docs/.idea/workspace.xml +++ /dev/null @@ -1,424 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - .io - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "; - - // Support: IE8, Opera 11-12.16 - // Nothing should be selected when empty strings follow ^= or $= or *= - // The test attribute must be unknown in Opera but "safe" for WinRT - // http://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section - if ( div.querySelectorAll("[msallowclip^='']").length ) { - rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); - } - - // Support: IE8 - // Boolean attributes and "value" are not treated correctly - if ( !div.querySelectorAll("[selected]").length ) { - rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); - } - - // Webkit/Opera - :checked should return selected option elements - // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked - // IE8 throws error here and will not see later tests - if ( !div.querySelectorAll(":checked").length ) { - rbuggyQSA.push(":checked"); - } - }); - - assert(function( div ) { - // Support: Windows 8 Native Apps - // The type and name attributes are restricted during .innerHTML assignment - var input = doc.createElement("input"); - input.setAttribute( "type", "hidden" ); - div.appendChild( input ).setAttribute( "name", "D" ); - - // Support: IE8 - // Enforce case-sensitivity of name attribute - if ( div.querySelectorAll("[name=d]").length ) { - rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); - } - - // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) - // IE8 throws error here and will not see later tests - if ( !div.querySelectorAll(":enabled").length ) { - rbuggyQSA.push( ":enabled", ":disabled" ); - } - - // Opera 10-11 does not throw on post-comma invalid pseudos - div.querySelectorAll("*,:x"); - rbuggyQSA.push(",.*:"); - }); - } - - if ( (support.matchesSelector = rnative.test( (matches = docElem.matches || - docElem.webkitMatchesSelector || - docElem.mozMatchesSelector || - docElem.oMatchesSelector || - docElem.msMatchesSelector) )) ) { - - assert(function( div ) { - // Check to see if it's possible to do matchesSelector - // on a disconnected node (IE 9) - support.disconnectedMatch = matches.call( div, "div" ); - - // This should fail with an exception - // Gecko does not error, returns false instead - matches.call( div, "[s!='']:x" ); - rbuggyMatches.push( "!=", pseudos ); - }); - } - - rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") ); - rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") ); - - /* Contains - ---------------------------------------------------------------------- */ - hasCompare = rnative.test( docElem.compareDocumentPosition ); - - // Element contains another - // Purposefully does not implement inclusive descendent - // As in, an element does not contain itself - contains = hasCompare || rnative.test( docElem.contains ) ? - function( a, b ) { - var adown = a.nodeType === 9 ? a.documentElement : a, - bup = b && b.parentNode; - return a === bup || !!( bup && bup.nodeType === 1 && ( - adown.contains ? - adown.contains( bup ) : - a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 - )); - } : - function( a, b ) { - if ( b ) { - while ( (b = b.parentNode) ) { - if ( b === a ) { - return true; - } - } - } - return false; - }; - - /* Sorting - ---------------------------------------------------------------------- */ - - // Document order sorting - sortOrder = hasCompare ? - function( a, b ) { - - // Flag for duplicate removal - if ( a === b ) { - hasDuplicate = true; - return 0; - } - - // Sort on method existence if only one input has compareDocumentPosition - var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; - if ( compare ) { - return compare; - } - - // Calculate position if both inputs belong to the same document - compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ? - a.compareDocumentPosition( b ) : - - // Otherwise we know they are disconnected - 1; - - // Disconnected nodes - if ( compare & 1 || - (!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) { - - // Choose the first element that is related to our preferred document - if ( a === doc || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) { - return -1; - } - if ( b === doc || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) { - return 1; - } - - // Maintain original order - return sortInput ? - ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) : - 0; - } - - return compare & 4 ? -1 : 1; - } : - function( a, b ) { - // Exit early if the nodes are identical - if ( a === b ) { - hasDuplicate = true; - return 0; - } - - var cur, - i = 0, - aup = a.parentNode, - bup = b.parentNode, - ap = [ a ], - bp = [ b ]; - - // Parentless nodes are either documents or disconnected - if ( !aup || !bup ) { - return a === doc ? -1 : - b === doc ? 1 : - aup ? -1 : - bup ? 1 : - sortInput ? - ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) : - 0; - - // If the nodes are siblings, we can do a quick check - } else if ( aup === bup ) { - return siblingCheck( a, b ); - } - - // Otherwise we need full lists of their ancestors for comparison - cur = a; - while ( (cur = cur.parentNode) ) { - ap.unshift( cur ); - } - cur = b; - while ( (cur = cur.parentNode) ) { - bp.unshift( cur ); - } - - // Walk down the tree looking for a discrepancy - while ( ap[i] === bp[i] ) { - i++; - } - - return i ? - // Do a sibling check if the nodes have a common ancestor - siblingCheck( ap[i], bp[i] ) : - - // Otherwise nodes in our document sort first - ap[i] === preferredDoc ? -1 : - bp[i] === preferredDoc ? 1 : - 0; - }; - - return doc; -}; - -Sizzle.matches = function( expr, elements ) { - return Sizzle( expr, null, null, elements ); -}; - -Sizzle.matchesSelector = function( elem, expr ) { - // Set document vars if needed - if ( ( elem.ownerDocument || elem ) !== document ) { - setDocument( elem ); - } - - // Make sure that attribute selectors are quoted - expr = expr.replace( rattributeQuotes, "='$1']" ); - - if ( support.matchesSelector && documentIsHTML && - ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && - ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { - - try { - var ret = matches.call( elem, expr ); - - // IE 9's matchesSelector returns false on disconnected nodes - if ( ret || support.disconnectedMatch || - // As well, disconnected nodes are said to be in a document - // fragment in IE 9 - elem.document && elem.document.nodeType !== 11 ) { - return ret; - } - } catch(e) {} - } - - return Sizzle( expr, document, null, [ elem ] ).length > 0; -}; - -Sizzle.contains = function( context, elem ) { - // Set document vars if needed - if ( ( context.ownerDocument || context ) !== document ) { - setDocument( context ); - } - return contains( context, elem ); -}; - -Sizzle.attr = function( elem, name ) { - // Set document vars if needed - if ( ( elem.ownerDocument || elem ) !== document ) { - setDocument( elem ); - } - - var fn = Expr.attrHandle[ name.toLowerCase() ], - // Don't get fooled by Object.prototype properties (jQuery #13807) - val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? - fn( elem, name, !documentIsHTML ) : - undefined; - - return val !== undefined ? - val : - support.attributes || !documentIsHTML ? - elem.getAttribute( name ) : - (val = elem.getAttributeNode(name)) && val.specified ? - val.value : - null; -}; - -Sizzle.error = function( msg ) { - throw new Error( "Syntax error, unrecognized expression: " + msg ); -}; - -/** - * Document sorting and removing duplicates - * @param {ArrayLike} results - */ -Sizzle.uniqueSort = function( results ) { - var elem, - duplicates = [], - j = 0, - i = 0; - - // Unless we *know* we can detect duplicates, assume their presence - hasDuplicate = !support.detectDuplicates; - sortInput = !support.sortStable && results.slice( 0 ); - results.sort( sortOrder ); - - if ( hasDuplicate ) { - while ( (elem = results[i++]) ) { - if ( elem === results[ i ] ) { - j = duplicates.push( i ); - } - } - while ( j-- ) { - results.splice( duplicates[ j ], 1 ); - } - } - - // Clear input after sorting to release objects - // See https://github.com/jquery/sizzle/pull/225 - sortInput = null; - - return results; -}; - -/** - * Utility function for retrieving the text value of an array of DOM nodes - * @param {Array|Element} elem - */ -getText = Sizzle.getText = function( elem ) { - var node, - ret = "", - i = 0, - nodeType = elem.nodeType; - - if ( !nodeType ) { - // If no nodeType, this is expected to be an array - while ( (node = elem[i++]) ) { - // Do not traverse comment nodes - ret += getText( node ); - } - } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { - // Use textContent for elements - // innerText usage removed for consistency of new lines (jQuery #11153) - if ( typeof elem.textContent === "string" ) { - return elem.textContent; - } else { - // Traverse its children - for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { - ret += getText( elem ); - } - } - } else if ( nodeType === 3 || nodeType === 4 ) { - return elem.nodeValue; - } - // Do not include comment or processing instruction nodes - - return ret; -}; - -Expr = Sizzle.selectors = { - - // Can be adjusted by the user - cacheLength: 50, - - createPseudo: markFunction, - - match: matchExpr, - - attrHandle: {}, - - find: {}, - - relative: { - ">": { dir: "parentNode", first: true }, - " ": { dir: "parentNode" }, - "+": { dir: "previousSibling", first: true }, - "~": { dir: "previousSibling" } - }, - - preFilter: { - "ATTR": function( match ) { - match[1] = match[1].replace( runescape, funescape ); - - // Move the given value to match[3] whether quoted or unquoted - match[3] = ( match[3] || match[4] || match[5] || "" ).replace( runescape, funescape ); - - if ( match[2] === "~=" ) { - match[3] = " " + match[3] + " "; - } - - return match.slice( 0, 4 ); - }, - - "CHILD": function( match ) { - /* matches from matchExpr["CHILD"] - 1 type (only|nth|...) - 2 what (child|of-type) - 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) - 4 xn-component of xn+y argument ([+-]?\d*n|) - 5 sign of xn-component - 6 x of xn-component - 7 sign of y-component - 8 y of y-component - */ - match[1] = match[1].toLowerCase(); - - if ( match[1].slice( 0, 3 ) === "nth" ) { - // nth-* requires argument - if ( !match[3] ) { - Sizzle.error( match[0] ); - } - - // numeric x and y parameters for Expr.filter.CHILD - // remember that false/true cast respectively to 0/1 - match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) ); - match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" ); - - // other types prohibit arguments - } else if ( match[3] ) { - Sizzle.error( match[0] ); - } - - return match; - }, - - "PSEUDO": function( match ) { - var excess, - unquoted = !match[6] && match[2]; - - if ( matchExpr["CHILD"].test( match[0] ) ) { - return null; - } - - // Accept quoted arguments as-is - if ( match[3] ) { - match[2] = match[4] || match[5] || ""; - - // Strip excess characters from unquoted arguments - } else if ( unquoted && rpseudo.test( unquoted ) && - // Get excess from tokenize (recursively) - (excess = tokenize( unquoted, true )) && - // advance to the next closing parenthesis - (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) { - - // excess is a negative index - match[0] = match[0].slice( 0, excess ); - match[2] = unquoted.slice( 0, excess ); - } - - // Return only captures needed by the pseudo filter method (type and argument) - return match.slice( 0, 3 ); - } - }, - - filter: { - - "TAG": function( nodeNameSelector ) { - var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); - return nodeNameSelector === "*" ? - function() { return true; } : - function( elem ) { - return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; - }; - }, - - "CLASS": function( className ) { - var pattern = classCache[ className + " " ]; - - return pattern || - (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) && - classCache( className, function( elem ) { - return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== strundefined && elem.getAttribute("class") || "" ); - }); - }, - - "ATTR": function( name, operator, check ) { - return function( elem ) { - var result = Sizzle.attr( elem, name ); - - if ( result == null ) { - return operator === "!="; - } - if ( !operator ) { - return true; - } - - result += ""; - - return operator === "=" ? result === check : - operator === "!=" ? result !== check : - operator === "^=" ? check && result.indexOf( check ) === 0 : - operator === "*=" ? check && result.indexOf( check ) > -1 : - operator === "$=" ? check && result.slice( -check.length ) === check : - operator === "~=" ? ( " " + result + " " ).indexOf( check ) > -1 : - operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : - false; - }; - }, - - "CHILD": function( type, what, argument, first, last ) { - var simple = type.slice( 0, 3 ) !== "nth", - forward = type.slice( -4 ) !== "last", - ofType = what === "of-type"; - - return first === 1 && last === 0 ? - - // Shortcut for :nth-*(n) - function( elem ) { - return !!elem.parentNode; - } : - - function( elem, context, xml ) { - var cache, outerCache, node, diff, nodeIndex, start, - dir = simple !== forward ? "nextSibling" : "previousSibling", - parent = elem.parentNode, - name = ofType && elem.nodeName.toLowerCase(), - useCache = !xml && !ofType; - - if ( parent ) { - - // :(first|last|only)-(child|of-type) - if ( simple ) { - while ( dir ) { - node = elem; - while ( (node = node[ dir ]) ) { - if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) { - return false; - } - } - // Reverse direction for :only-* (if we haven't yet done so) - start = dir = type === "only" && !start && "nextSibling"; - } - return true; - } - - start = [ forward ? parent.firstChild : parent.lastChild ]; - - // non-xml :nth-child(...) stores cache data on `parent` - if ( forward && useCache ) { - // Seek `elem` from a previously-cached index - outerCache = parent[ expando ] || (parent[ expando ] = {}); - cache = outerCache[ type ] || []; - nodeIndex = cache[0] === dirruns && cache[1]; - diff = cache[0] === dirruns && cache[2]; - node = nodeIndex && parent.childNodes[ nodeIndex ]; - - while ( (node = ++nodeIndex && node && node[ dir ] || - - // Fallback to seeking `elem` from the start - (diff = nodeIndex = 0) || start.pop()) ) { - - // When found, cache indexes on `parent` and break - if ( node.nodeType === 1 && ++diff && node === elem ) { - outerCache[ type ] = [ dirruns, nodeIndex, diff ]; - break; - } - } - - // Use previously-cached element index if available - } else if ( useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] === dirruns ) { - diff = cache[1]; - - // xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...) - } else { - // Use the same loop as above to seek `elem` from the start - while ( (node = ++nodeIndex && node && node[ dir ] || - (diff = nodeIndex = 0) || start.pop()) ) { - - if ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) { - // Cache the index of each encountered element - if ( useCache ) { - (node[ expando ] || (node[ expando ] = {}))[ type ] = [ dirruns, diff ]; - } - - if ( node === elem ) { - break; - } - } - } - } - - // Incorporate the offset, then check against cycle size - diff -= last; - return diff === first || ( diff % first === 0 && diff / first >= 0 ); - } - }; - }, - - "PSEUDO": function( pseudo, argument ) { - // pseudo-class names are case-insensitive - // http://www.w3.org/TR/selectors/#pseudo-classes - // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters - // Remember that setFilters inherits from pseudos - var args, - fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || - Sizzle.error( "unsupported pseudo: " + pseudo ); - - // The user may use createPseudo to indicate that - // arguments are needed to create the filter function - // just as Sizzle does - if ( fn[ expando ] ) { - return fn( argument ); - } - - // But maintain support for old signatures - if ( fn.length > 1 ) { - args = [ pseudo, pseudo, "", argument ]; - return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? - markFunction(function( seed, matches ) { - var idx, - matched = fn( seed, argument ), - i = matched.length; - while ( i-- ) { - idx = indexOf.call( seed, matched[i] ); - seed[ idx ] = !( matches[ idx ] = matched[i] ); - } - }) : - function( elem ) { - return fn( elem, 0, args ); - }; - } - - return fn; - } - }, - - pseudos: { - // Potentially complex pseudos - "not": markFunction(function( selector ) { - // Trim the selector passed to compile - // to avoid treating leading and trailing - // spaces as combinators - var input = [], - results = [], - matcher = compile( selector.replace( rtrim, "$1" ) ); - - return matcher[ expando ] ? - markFunction(function( seed, matches, context, xml ) { - var elem, - unmatched = matcher( seed, null, xml, [] ), - i = seed.length; - - // Match elements unmatched by `matcher` - while ( i-- ) { - if ( (elem = unmatched[i]) ) { - seed[i] = !(matches[i] = elem); - } - } - }) : - function( elem, context, xml ) { - input[0] = elem; - matcher( input, null, xml, results ); - return !results.pop(); - }; - }), - - "has": markFunction(function( selector ) { - return function( elem ) { - return Sizzle( selector, elem ).length > 0; - }; - }), - - "contains": markFunction(function( text ) { - return function( elem ) { - return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1; - }; - }), - - // "Whether an element is represented by a :lang() selector - // is based solely on the element's language value - // being equal to the identifier C, - // or beginning with the identifier C immediately followed by "-". - // The matching of C against the element's language value is performed case-insensitively. - // The identifier C does not have to be a valid language name." - // http://www.w3.org/TR/selectors/#lang-pseudo - "lang": markFunction( function( lang ) { - // lang value must be a valid identifier - if ( !ridentifier.test(lang || "") ) { - Sizzle.error( "unsupported lang: " + lang ); - } - lang = lang.replace( runescape, funescape ).toLowerCase(); - return function( elem ) { - var elemLang; - do { - if ( (elemLang = documentIsHTML ? - elem.lang : - elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) { - - elemLang = elemLang.toLowerCase(); - return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; - } - } while ( (elem = elem.parentNode) && elem.nodeType === 1 ); - return false; - }; - }), - - // Miscellaneous - "target": function( elem ) { - var hash = window.location && window.location.hash; - return hash && hash.slice( 1 ) === elem.id; - }, - - "root": function( elem ) { - return elem === docElem; - }, - - "focus": function( elem ) { - return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); - }, - - // Boolean properties - "enabled": function( elem ) { - return elem.disabled === false; - }, - - "disabled": function( elem ) { - return elem.disabled === true; - }, - - "checked": function( elem ) { - // In CSS3, :checked should return both checked and selected elements - // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked - var nodeName = elem.nodeName.toLowerCase(); - return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected); - }, - - "selected": function( elem ) { - // Accessing this property makes selected-by-default - // options in Safari work properly - if ( elem.parentNode ) { - elem.parentNode.selectedIndex; - } - - return elem.selected === true; - }, - - // Contents - "empty": function( elem ) { - // http://www.w3.org/TR/selectors/#empty-pseudo - // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), - // but not by others (comment: 8; processing instruction: 7; etc.) - // nodeType < 6 works because attributes (2) do not appear as children - for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { - if ( elem.nodeType < 6 ) { - return false; - } - } - return true; - }, - - "parent": function( elem ) { - return !Expr.pseudos["empty"]( elem ); - }, - - // Element/input types - "header": function( elem ) { - return rheader.test( elem.nodeName ); - }, - - "input": function( elem ) { - return rinputs.test( elem.nodeName ); - }, - - "button": function( elem ) { - var name = elem.nodeName.toLowerCase(); - return name === "input" && elem.type === "button" || name === "button"; - }, - - "text": function( elem ) { - var attr; - return elem.nodeName.toLowerCase() === "input" && - elem.type === "text" && - - // Support: IE<8 - // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" - ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" ); - }, - - // Position-in-collection - "first": createPositionalPseudo(function() { - return [ 0 ]; - }), - - "last": createPositionalPseudo(function( matchIndexes, length ) { - return [ length - 1 ]; - }), - - "eq": createPositionalPseudo(function( matchIndexes, length, argument ) { - return [ argument < 0 ? argument + length : argument ]; - }), - - "even": createPositionalPseudo(function( matchIndexes, length ) { - var i = 0; - for ( ; i < length; i += 2 ) { - matchIndexes.push( i ); - } - return matchIndexes; - }), - - "odd": createPositionalPseudo(function( matchIndexes, length ) { - var i = 1; - for ( ; i < length; i += 2 ) { - matchIndexes.push( i ); - } - return matchIndexes; - }), - - "lt": createPositionalPseudo(function( matchIndexes, length, argument ) { - var i = argument < 0 ? argument + length : argument; - for ( ; --i >= 0; ) { - matchIndexes.push( i ); - } - return matchIndexes; - }), - - "gt": createPositionalPseudo(function( matchIndexes, length, argument ) { - var i = argument < 0 ? argument + length : argument; - for ( ; ++i < length; ) { - matchIndexes.push( i ); - } - return matchIndexes; - }) - } -}; - -Expr.pseudos["nth"] = Expr.pseudos["eq"]; - -// Add button/input type pseudos -for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { - Expr.pseudos[ i ] = createInputPseudo( i ); -} -for ( i in { submit: true, reset: true } ) { - Expr.pseudos[ i ] = createButtonPseudo( i ); -} - -// Easy API for creating new setFilters -function setFilters() {} -setFilters.prototype = Expr.filters = Expr.pseudos; -Expr.setFilters = new setFilters(); - -tokenize = Sizzle.tokenize = function( selector, parseOnly ) { - var matched, match, tokens, type, - soFar, groups, preFilters, - cached = tokenCache[ selector + " " ]; - - if ( cached ) { - return parseOnly ? 0 : cached.slice( 0 ); - } - - soFar = selector; - groups = []; - preFilters = Expr.preFilter; - - while ( soFar ) { - - // Comma and first run - if ( !matched || (match = rcomma.exec( soFar )) ) { - if ( match ) { - // Don't consume trailing commas as valid - soFar = soFar.slice( match[0].length ) || soFar; - } - groups.push( (tokens = []) ); - } - - matched = false; - - // Combinators - if ( (match = rcombinators.exec( soFar )) ) { - matched = match.shift(); - tokens.push({ - value: matched, - // Cast descendant combinators to space - type: match[0].replace( rtrim, " " ) - }); - soFar = soFar.slice( matched.length ); - } - - // Filters - for ( type in Expr.filter ) { - if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] || - (match = preFilters[ type ]( match ))) ) { - matched = match.shift(); - tokens.push({ - value: matched, - type: type, - matches: match - }); - soFar = soFar.slice( matched.length ); - } - } - - if ( !matched ) { - break; - } - } - - // Return the length of the invalid excess - // if we're just parsing - // Otherwise, throw an error or return tokens - return parseOnly ? - soFar.length : - soFar ? - Sizzle.error( selector ) : - // Cache the tokens - tokenCache( selector, groups ).slice( 0 ); -}; - -function toSelector( tokens ) { - var i = 0, - len = tokens.length, - selector = ""; - for ( ; i < len; i++ ) { - selector += tokens[i].value; - } - return selector; -} - -function addCombinator( matcher, combinator, base ) { - var dir = combinator.dir, - checkNonElements = base && dir === "parentNode", - doneName = done++; - - return combinator.first ? - // Check against closest ancestor/preceding element - function( elem, context, xml ) { - while ( (elem = elem[ dir ]) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - return matcher( elem, context, xml ); - } - } - } : - - // Check against all ancestor/preceding elements - function( elem, context, xml ) { - var oldCache, outerCache, - newCache = [ dirruns, doneName ]; - - // We can't set arbitrary data on XML nodes, so they don't benefit from dir caching - if ( xml ) { - while ( (elem = elem[ dir ]) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - if ( matcher( elem, context, xml ) ) { - return true; - } - } - } - } else { - while ( (elem = elem[ dir ]) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - outerCache = elem[ expando ] || (elem[ expando ] = {}); - if ( (oldCache = outerCache[ dir ]) && - oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { - - // Assign to newCache so results back-propagate to previous elements - return (newCache[ 2 ] = oldCache[ 2 ]); - } else { - // Reuse newcache so results back-propagate to previous elements - outerCache[ dir ] = newCache; - - // A match means we're done; a fail means we have to keep checking - if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) { - return true; - } - } - } - } - } - }; -} - -function elementMatcher( matchers ) { - return matchers.length > 1 ? - function( elem, context, xml ) { - var i = matchers.length; - while ( i-- ) { - if ( !matchers[i]( elem, context, xml ) ) { - return false; - } - } - return true; - } : - matchers[0]; -} - -function multipleContexts( selector, contexts, results ) { - var i = 0, - len = contexts.length; - for ( ; i < len; i++ ) { - Sizzle( selector, contexts[i], results ); - } - return results; -} - -function condense( unmatched, map, filter, context, xml ) { - var elem, - newUnmatched = [], - i = 0, - len = unmatched.length, - mapped = map != null; - - for ( ; i < len; i++ ) { - if ( (elem = unmatched[i]) ) { - if ( !filter || filter( elem, context, xml ) ) { - newUnmatched.push( elem ); - if ( mapped ) { - map.push( i ); - } - } - } - } - - return newUnmatched; -} - -function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { - if ( postFilter && !postFilter[ expando ] ) { - postFilter = setMatcher( postFilter ); - } - if ( postFinder && !postFinder[ expando ] ) { - postFinder = setMatcher( postFinder, postSelector ); - } - return markFunction(function( seed, results, context, xml ) { - var temp, i, elem, - preMap = [], - postMap = [], - preexisting = results.length, - - // Get initial elements from seed or context - elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ), - - // Prefilter to get matcher input, preserving a map for seed-results synchronization - matcherIn = preFilter && ( seed || !selector ) ? - condense( elems, preMap, preFilter, context, xml ) : - elems, - - matcherOut = matcher ? - // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, - postFinder || ( seed ? preFilter : preexisting || postFilter ) ? - - // ...intermediate processing is necessary - [] : - - // ...otherwise use results directly - results : - matcherIn; - - // Find primary matches - if ( matcher ) { - matcher( matcherIn, matcherOut, context, xml ); - } - - // Apply postFilter - if ( postFilter ) { - temp = condense( matcherOut, postMap ); - postFilter( temp, [], context, xml ); - - // Un-match failing elements by moving them back to matcherIn - i = temp.length; - while ( i-- ) { - if ( (elem = temp[i]) ) { - matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem); - } - } - } - - if ( seed ) { - if ( postFinder || preFilter ) { - if ( postFinder ) { - // Get the final matcherOut by condensing this intermediate into postFinder contexts - temp = []; - i = matcherOut.length; - while ( i-- ) { - if ( (elem = matcherOut[i]) ) { - // Restore matcherIn since elem is not yet a final match - temp.push( (matcherIn[i] = elem) ); - } - } - postFinder( null, (matcherOut = []), temp, xml ); - } - - // Move matched elements from seed to results to keep them synchronized - i = matcherOut.length; - while ( i-- ) { - if ( (elem = matcherOut[i]) && - (temp = postFinder ? indexOf.call( seed, elem ) : preMap[i]) > -1 ) { - - seed[temp] = !(results[temp] = elem); - } - } - } - - // Add elements to results, through postFinder if defined - } else { - matcherOut = condense( - matcherOut === results ? - matcherOut.splice( preexisting, matcherOut.length ) : - matcherOut - ); - if ( postFinder ) { - postFinder( null, results, matcherOut, xml ); - } else { - push.apply( results, matcherOut ); - } - } - }); -} - -function matcherFromTokens( tokens ) { - var checkContext, matcher, j, - len = tokens.length, - leadingRelative = Expr.relative[ tokens[0].type ], - implicitRelative = leadingRelative || Expr.relative[" "], - i = leadingRelative ? 1 : 0, - - // The foundational matcher ensures that elements are reachable from top-level context(s) - matchContext = addCombinator( function( elem ) { - return elem === checkContext; - }, implicitRelative, true ), - matchAnyContext = addCombinator( function( elem ) { - return indexOf.call( checkContext, elem ) > -1; - }, implicitRelative, true ), - matchers = [ function( elem, context, xml ) { - return ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( - (checkContext = context).nodeType ? - matchContext( elem, context, xml ) : - matchAnyContext( elem, context, xml ) ); - } ]; - - for ( ; i < len; i++ ) { - if ( (matcher = Expr.relative[ tokens[i].type ]) ) { - matchers = [ addCombinator(elementMatcher( matchers ), matcher) ]; - } else { - matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches ); - - // Return special upon seeing a positional matcher - if ( matcher[ expando ] ) { - // Find the next relative operator (if any) for proper handling - j = ++i; - for ( ; j < len; j++ ) { - if ( Expr.relative[ tokens[j].type ] ) { - break; - } - } - return setMatcher( - i > 1 && elementMatcher( matchers ), - i > 1 && toSelector( - // If the preceding token was a descendant combinator, insert an implicit any-element `*` - tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" }) - ).replace( rtrim, "$1" ), - matcher, - i < j && matcherFromTokens( tokens.slice( i, j ) ), - j < len && matcherFromTokens( (tokens = tokens.slice( j )) ), - j < len && toSelector( tokens ) - ); - } - matchers.push( matcher ); - } - } - - return elementMatcher( matchers ); -} - -function matcherFromGroupMatchers( elementMatchers, setMatchers ) { - var bySet = setMatchers.length > 0, - byElement = elementMatchers.length > 0, - superMatcher = function( seed, context, xml, results, outermost ) { - var elem, j, matcher, - matchedCount = 0, - i = "0", - unmatched = seed && [], - setMatched = [], - contextBackup = outermostContext, - // We must always have either seed elements or outermost context - elems = seed || byElement && Expr.find["TAG"]( "*", outermost ), - // Use integer dirruns iff this is the outermost matcher - dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1), - len = elems.length; - - if ( outermost ) { - outermostContext = context !== document && context; - } - - // Add elements passing elementMatchers directly to results - // Keep `i` a string if there are no elements so `matchedCount` will be "00" below - // Support: IE<9, Safari - // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id - for ( ; i !== len && (elem = elems[i]) != null; i++ ) { - if ( byElement && elem ) { - j = 0; - while ( (matcher = elementMatchers[j++]) ) { - if ( matcher( elem, context, xml ) ) { - results.push( elem ); - break; - } - } - if ( outermost ) { - dirruns = dirrunsUnique; - } - } - - // Track unmatched elements for set filters - if ( bySet ) { - // They will have gone through all possible matchers - if ( (elem = !matcher && elem) ) { - matchedCount--; - } - - // Lengthen the array for every element, matched or not - if ( seed ) { - unmatched.push( elem ); - } - } - } - - // Apply set filters to unmatched elements - matchedCount += i; - if ( bySet && i !== matchedCount ) { - j = 0; - while ( (matcher = setMatchers[j++]) ) { - matcher( unmatched, setMatched, context, xml ); - } - - if ( seed ) { - // Reintegrate element matches to eliminate the need for sorting - if ( matchedCount > 0 ) { - while ( i-- ) { - if ( !(unmatched[i] || setMatched[i]) ) { - setMatched[i] = pop.call( results ); - } - } - } - - // Discard index placeholder values to get only actual matches - setMatched = condense( setMatched ); - } - - // Add matches to results - push.apply( results, setMatched ); - - // Seedless set matches succeeding multiple successful matchers stipulate sorting - if ( outermost && !seed && setMatched.length > 0 && - ( matchedCount + setMatchers.length ) > 1 ) { - - Sizzle.uniqueSort( results ); - } - } - - // Override manipulation of globals by nested matchers - if ( outermost ) { - dirruns = dirrunsUnique; - outermostContext = contextBackup; - } - - return unmatched; - }; - - return bySet ? - markFunction( superMatcher ) : - superMatcher; -} - -compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { - var i, - setMatchers = [], - elementMatchers = [], - cached = compilerCache[ selector + " " ]; - - if ( !cached ) { - // Generate a function of recursive functions that can be used to check each element - if ( !match ) { - match = tokenize( selector ); - } - i = match.length; - while ( i-- ) { - cached = matcherFromTokens( match[i] ); - if ( cached[ expando ] ) { - setMatchers.push( cached ); - } else { - elementMatchers.push( cached ); - } - } - - // Cache the compiled function - cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) ); - - // Save selector and tokenization - cached.selector = selector; - } - return cached; -}; - -/** - * A low-level selection function that works with Sizzle's compiled - * selector functions - * @param {String|Function} selector A selector or a pre-compiled - * selector function built with Sizzle.compile - * @param {Element} context - * @param {Array} [results] - * @param {Array} [seed] A set of elements to match against - */ -select = Sizzle.select = function( selector, context, results, seed ) { - var i, tokens, token, type, find, - compiled = typeof selector === "function" && selector, - match = !seed && tokenize( (selector = compiled.selector || selector) ); - - results = results || []; - - // Try to minimize operations if there is no seed and only one group - if ( match.length === 1 ) { - - // Take a shortcut and set the context if the root selector is an ID - tokens = match[0] = match[0].slice( 0 ); - if ( tokens.length > 2 && (token = tokens[0]).type === "ID" && - support.getById && context.nodeType === 9 && documentIsHTML && - Expr.relative[ tokens[1].type ] ) { - - context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0]; - if ( !context ) { - return results; - - // Precompiled matchers will still verify ancestry, so step up a level - } else if ( compiled ) { - context = context.parentNode; - } - - selector = selector.slice( tokens.shift().value.length ); - } - - // Fetch a seed set for right-to-left matching - i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length; - while ( i-- ) { - token = tokens[i]; - - // Abort if we hit a combinator - if ( Expr.relative[ (type = token.type) ] ) { - break; - } - if ( (find = Expr.find[ type ]) ) { - // Search, expanding context for leading sibling combinators - if ( (seed = find( - token.matches[0].replace( runescape, funescape ), - rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context - )) ) { - - // If seed is empty or no tokens remain, we can return early - tokens.splice( i, 1 ); - selector = seed.length && toSelector( tokens ); - if ( !selector ) { - push.apply( results, seed ); - return results; - } - - break; - } - } - } - } - - // Compile and execute a filtering function if one is not provided - // Provide `match` to avoid retokenization if we modified the selector above - ( compiled || compile( selector, match ) )( - seed, - context, - !documentIsHTML, - results, - rsibling.test( selector ) && testContext( context.parentNode ) || context - ); - return results; -}; - -// One-time assignments - -// Sort stability -support.sortStable = expando.split("").sort( sortOrder ).join("") === expando; - -// Support: Chrome<14 -// Always assume duplicates if they aren't passed to the comparison function -support.detectDuplicates = !!hasDuplicate; - -// Initialize against the default document -setDocument(); - -// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) -// Detached nodes confoundingly follow *each other* -support.sortDetached = assert(function( div1 ) { - // Should return 1, but returns 4 (following) - return div1.compareDocumentPosition( document.createElement("div") ) & 1; -}); - -// Support: IE<8 -// Prevent attribute/property "interpolation" -// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx -if ( !assert(function( div ) { - div.innerHTML = ""; - return div.firstChild.getAttribute("href") === "#" ; -}) ) { - addHandle( "type|href|height|width", function( elem, name, isXML ) { - if ( !isXML ) { - return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); - } - }); -} - -// Support: IE<9 -// Use defaultValue in place of getAttribute("value") -if ( !support.attributes || !assert(function( div ) { - div.innerHTML = ""; - div.firstChild.setAttribute( "value", "" ); - return div.firstChild.getAttribute( "value" ) === ""; -}) ) { - addHandle( "value", function( elem, name, isXML ) { - if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { - return elem.defaultValue; - } - }); -} - -// Support: IE<9 -// Use getAttributeNode to fetch booleans when getAttribute lies -if ( !assert(function( div ) { - return div.getAttribute("disabled") == null; -}) ) { - addHandle( booleans, function( elem, name, isXML ) { - var val; - if ( !isXML ) { - return elem[ name ] === true ? name.toLowerCase() : - (val = elem.getAttributeNode( name )) && val.specified ? - val.value : - null; - } - }); -} - -return Sizzle; - -})( window ); - - - -jQuery.find = Sizzle; -jQuery.expr = Sizzle.selectors; -jQuery.expr[":"] = jQuery.expr.pseudos; -jQuery.unique = Sizzle.uniqueSort; -jQuery.text = Sizzle.getText; -jQuery.isXMLDoc = Sizzle.isXML; -jQuery.contains = Sizzle.contains; - - - -var rneedsContext = jQuery.expr.match.needsContext; - -var rsingleTag = (/^<(\w+)\s*\/?>(?:<\/\1>|)$/); - - - -var risSimple = /^.[^:#\[\.,]*$/; - -// Implement the identical functionality for filter and not -function winnow( elements, qualifier, not ) { - if ( jQuery.isFunction( qualifier ) ) { - return jQuery.grep( elements, function( elem, i ) { - /* jshint -W018 */ - return !!qualifier.call( elem, i, elem ) !== not; - }); - - } - - if ( qualifier.nodeType ) { - return jQuery.grep( elements, function( elem ) { - return ( elem === qualifier ) !== not; - }); - - } - - if ( typeof qualifier === "string" ) { - if ( risSimple.test( qualifier ) ) { - return jQuery.filter( qualifier, elements, not ); - } - - qualifier = jQuery.filter( qualifier, elements ); - } - - return jQuery.grep( elements, function( elem ) { - return ( jQuery.inArray( elem, qualifier ) >= 0 ) !== not; - }); -} - -jQuery.filter = function( expr, elems, not ) { - var elem = elems[ 0 ]; - - if ( not ) { - expr = ":not(" + expr + ")"; - } - - return elems.length === 1 && elem.nodeType === 1 ? - jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [] : - jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { - return elem.nodeType === 1; - })); -}; - -jQuery.fn.extend({ - find: function( selector ) { - var i, - ret = [], - self = this, - len = self.length; - - if ( typeof selector !== "string" ) { - return this.pushStack( jQuery( selector ).filter(function() { - for ( i = 0; i < len; i++ ) { - if ( jQuery.contains( self[ i ], this ) ) { - return true; - } - } - }) ); - } - - for ( i = 0; i < len; i++ ) { - jQuery.find( selector, self[ i ], ret ); - } - - // Needed because $( selector, context ) becomes $( context ).find( selector ) - ret = this.pushStack( len > 1 ? jQuery.unique( ret ) : ret ); - ret.selector = this.selector ? this.selector + " " + selector : selector; - return ret; - }, - filter: function( selector ) { - return this.pushStack( winnow(this, selector || [], false) ); - }, - not: function( selector ) { - return this.pushStack( winnow(this, selector || [], true) ); - }, - is: function( selector ) { - return !!winnow( - this, - - // If this is a positional/relative selector, check membership in the returned set - // so $("p:first").is("p:last") won't return true for a doc with two "p". - typeof selector === "string" && rneedsContext.test( selector ) ? - jQuery( selector ) : - selector || [], - false - ).length; - } -}); - - -// Initialize a jQuery object - - -// A central reference to the root jQuery(document) -var rootjQuery, - - // Use the correct document accordingly with window argument (sandbox) - document = window.document, - - // A simple way to check for HTML strings - // Prioritize #id over to avoid XSS via location.hash (#9521) - // Strict HTML recognition (#11290: must start with <) - rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/, - - init = jQuery.fn.init = function( selector, context ) { - var match, elem; - - // HANDLE: $(""), $(null), $(undefined), $(false) - if ( !selector ) { - return this; - } - - // Handle HTML strings - if ( typeof selector === "string" ) { - if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) { - // Assume that strings that start and end with <> are HTML and skip the regex check - match = [ null, selector, null ]; - - } else { - match = rquickExpr.exec( selector ); - } - - // Match html or make sure no context is specified for #id - if ( match && (match[1] || !context) ) { - - // HANDLE: $(html) -> $(array) - if ( match[1] ) { - context = context instanceof jQuery ? context[0] : context; - - // scripts is true for back-compat - // Intentionally let the error be thrown if parseHTML is not present - jQuery.merge( this, jQuery.parseHTML( - match[1], - context && context.nodeType ? context.ownerDocument || context : document, - true - ) ); - - // HANDLE: $(html, props) - if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) { - for ( match in context ) { - // Properties of context are called as methods if possible - if ( jQuery.isFunction( this[ match ] ) ) { - this[ match ]( context[ match ] ); - - // ...and otherwise set as attributes - } else { - this.attr( match, context[ match ] ); - } - } - } - - return this; - - // HANDLE: $(#id) - } else { - elem = document.getElementById( match[2] ); - - // Check parentNode to catch when Blackberry 4.6 returns - // nodes that are no longer in the document #6963 - if ( elem && elem.parentNode ) { - // Handle the case where IE and Opera return items - // by name instead of ID - if ( elem.id !== match[2] ) { - return rootjQuery.find( selector ); - } - - // Otherwise, we inject the element directly into the jQuery object - this.length = 1; - this[0] = elem; - } - - this.context = document; - this.selector = selector; - return this; - } - - // HANDLE: $(expr, $(...)) - } else if ( !context || context.jquery ) { - return ( context || rootjQuery ).find( selector ); - - // HANDLE: $(expr, context) - // (which is just equivalent to: $(context).find(expr) - } else { - return this.constructor( context ).find( selector ); - } - - // HANDLE: $(DOMElement) - } else if ( selector.nodeType ) { - this.context = this[0] = selector; - this.length = 1; - return this; - - // HANDLE: $(function) - // Shortcut for document ready - } else if ( jQuery.isFunction( selector ) ) { - return typeof rootjQuery.ready !== "undefined" ? - rootjQuery.ready( selector ) : - // Execute immediately if ready is not present - selector( jQuery ); - } - - if ( selector.selector !== undefined ) { - this.selector = selector.selector; - this.context = selector.context; - } - - return jQuery.makeArray( selector, this ); - }; - -// Give the init function the jQuery prototype for later instantiation -init.prototype = jQuery.fn; - -// Initialize central reference -rootjQuery = jQuery( document ); - - -var rparentsprev = /^(?:parents|prev(?:Until|All))/, - // methods guaranteed to produce a unique set when starting from a unique set - guaranteedUnique = { - children: true, - contents: true, - next: true, - prev: true - }; - -jQuery.extend({ - dir: function( elem, dir, until ) { - var matched = [], - cur = elem[ dir ]; - - while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) { - if ( cur.nodeType === 1 ) { - matched.push( cur ); - } - cur = cur[dir]; - } - return matched; - }, - - sibling: function( n, elem ) { - var r = []; - - for ( ; n; n = n.nextSibling ) { - if ( n.nodeType === 1 && n !== elem ) { - r.push( n ); - } - } - - return r; - } -}); - -jQuery.fn.extend({ - has: function( target ) { - var i, - targets = jQuery( target, this ), - len = targets.length; - - return this.filter(function() { - for ( i = 0; i < len; i++ ) { - if ( jQuery.contains( this, targets[i] ) ) { - return true; - } - } - }); - }, - - closest: function( selectors, context ) { - var cur, - i = 0, - l = this.length, - matched = [], - pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ? - jQuery( selectors, context || this.context ) : - 0; - - for ( ; i < l; i++ ) { - for ( cur = this[i]; cur && cur !== context; cur = cur.parentNode ) { - // Always skip document fragments - if ( cur.nodeType < 11 && (pos ? - pos.index(cur) > -1 : - - // Don't pass non-elements to Sizzle - cur.nodeType === 1 && - jQuery.find.matchesSelector(cur, selectors)) ) { - - matched.push( cur ); - break; - } - } - } - - return this.pushStack( matched.length > 1 ? jQuery.unique( matched ) : matched ); - }, - - // Determine the position of an element within - // the matched set of elements - index: function( elem ) { - - // No argument, return index in parent - if ( !elem ) { - return ( this[0] && this[0].parentNode ) ? this.first().prevAll().length : -1; - } - - // index in selector - if ( typeof elem === "string" ) { - return jQuery.inArray( this[0], jQuery( elem ) ); - } - - // Locate the position of the desired element - return jQuery.inArray( - // If it receives a jQuery object, the first element is used - elem.jquery ? elem[0] : elem, this ); - }, - - add: function( selector, context ) { - return this.pushStack( - jQuery.unique( - jQuery.merge( this.get(), jQuery( selector, context ) ) - ) - ); - }, - - addBack: function( selector ) { - return this.add( selector == null ? - this.prevObject : this.prevObject.filter(selector) - ); - } -}); - -function sibling( cur, dir ) { - do { - cur = cur[ dir ]; - } while ( cur && cur.nodeType !== 1 ); - - return cur; -} - -jQuery.each({ - parent: function( elem ) { - var parent = elem.parentNode; - return parent && parent.nodeType !== 11 ? parent : null; - }, - parents: function( elem ) { - return jQuery.dir( elem, "parentNode" ); - }, - parentsUntil: function( elem, i, until ) { - return jQuery.dir( elem, "parentNode", until ); - }, - next: function( elem ) { - return sibling( elem, "nextSibling" ); - }, - prev: function( elem ) { - return sibling( elem, "previousSibling" ); - }, - nextAll: function( elem ) { - return jQuery.dir( elem, "nextSibling" ); - }, - prevAll: function( elem ) { - return jQuery.dir( elem, "previousSibling" ); - }, - nextUntil: function( elem, i, until ) { - return jQuery.dir( elem, "nextSibling", until ); - }, - prevUntil: function( elem, i, until ) { - return jQuery.dir( elem, "previousSibling", until ); - }, - siblings: function( elem ) { - return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem ); - }, - children: function( elem ) { - return jQuery.sibling( elem.firstChild ); - }, - contents: function( elem ) { - return jQuery.nodeName( elem, "iframe" ) ? - elem.contentDocument || elem.contentWindow.document : - jQuery.merge( [], elem.childNodes ); - } -}, function( name, fn ) { - jQuery.fn[ name ] = function( until, selector ) { - var ret = jQuery.map( this, fn, until ); - - if ( name.slice( -5 ) !== "Until" ) { - selector = until; - } - - if ( selector && typeof selector === "string" ) { - ret = jQuery.filter( selector, ret ); - } - - if ( this.length > 1 ) { - // Remove duplicates - if ( !guaranteedUnique[ name ] ) { - ret = jQuery.unique( ret ); - } - - // Reverse order for parents* and prev-derivatives - if ( rparentsprev.test( name ) ) { - ret = ret.reverse(); - } - } - - return this.pushStack( ret ); - }; -}); -var rnotwhite = (/\S+/g); - - - -// String to Object options format cache -var optionsCache = {}; - -// Convert String-formatted options into Object-formatted ones and store in cache -function createOptions( options ) { - var object = optionsCache[ options ] = {}; - jQuery.each( options.match( rnotwhite ) || [], function( _, flag ) { - object[ flag ] = true; - }); - return object; -} - -/* - * Create a callback list using the following parameters: - * - * options: an optional list of space-separated options that will change how - * the callback list behaves or a more traditional option object - * - * By default a callback list will act like an event callback list and can be - * "fired" multiple times. - * - * Possible options: - * - * once: will ensure the callback list can only be fired once (like a Deferred) - * - * memory: will keep track of previous values and will call any callback added - * after the list has been fired right away with the latest "memorized" - * values (like a Deferred) - * - * unique: will ensure a callback can only be added once (no duplicate in the list) - * - * stopOnFalse: interrupt callings when a callback returns false - * - */ -jQuery.Callbacks = function( options ) { - - // Convert options from String-formatted to Object-formatted if needed - // (we check in cache first) - options = typeof options === "string" ? - ( optionsCache[ options ] || createOptions( options ) ) : - jQuery.extend( {}, options ); - - var // Flag to know if list is currently firing - firing, - // Last fire value (for non-forgettable lists) - memory, - // Flag to know if list was already fired - fired, - // End of the loop when firing - firingLength, - // Index of currently firing callback (modified by remove if needed) - firingIndex, - // First callback to fire (used internally by add and fireWith) - firingStart, - // Actual callback list - list = [], - // Stack of fire calls for repeatable lists - stack = !options.once && [], - // Fire callbacks - fire = function( data ) { - memory = options.memory && data; - fired = true; - firingIndex = firingStart || 0; - firingStart = 0; - firingLength = list.length; - firing = true; - for ( ; list && firingIndex < firingLength; firingIndex++ ) { - if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) { - memory = false; // To prevent further calls using add - break; - } - } - firing = false; - if ( list ) { - if ( stack ) { - if ( stack.length ) { - fire( stack.shift() ); - } - } else if ( memory ) { - list = []; - } else { - self.disable(); - } - } - }, - // Actual Callbacks object - self = { - // Add a callback or a collection of callbacks to the list - add: function() { - if ( list ) { - // First, we save the current length - var start = list.length; - (function add( args ) { - jQuery.each( args, function( _, arg ) { - var type = jQuery.type( arg ); - if ( type === "function" ) { - if ( !options.unique || !self.has( arg ) ) { - list.push( arg ); - } - } else if ( arg && arg.length && type !== "string" ) { - // Inspect recursively - add( arg ); - } - }); - })( arguments ); - // Do we need to add the callbacks to the - // current firing batch? - if ( firing ) { - firingLength = list.length; - // With memory, if we're not firing then - // we should call right away - } else if ( memory ) { - firingStart = start; - fire( memory ); - } - } - return this; - }, - // Remove a callback from the list - remove: function() { - if ( list ) { - jQuery.each( arguments, function( _, arg ) { - var index; - while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { - list.splice( index, 1 ); - // Handle firing indexes - if ( firing ) { - if ( index <= firingLength ) { - firingLength--; - } - if ( index <= firingIndex ) { - firingIndex--; - } - } - } - }); - } - return this; - }, - // Check if a given callback is in the list. - // If no argument is given, return whether or not list has callbacks attached. - has: function( fn ) { - return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length ); - }, - // Remove all callbacks from the list - empty: function() { - list = []; - firingLength = 0; - return this; - }, - // Have the list do nothing anymore - disable: function() { - list = stack = memory = undefined; - return this; - }, - // Is it disabled? - disabled: function() { - return !list; - }, - // Lock the list in its current state - lock: function() { - stack = undefined; - if ( !memory ) { - self.disable(); - } - return this; - }, - // Is it locked? - locked: function() { - return !stack; - }, - // Call all callbacks with the given context and arguments - fireWith: function( context, args ) { - if ( list && ( !fired || stack ) ) { - args = args || []; - args = [ context, args.slice ? args.slice() : args ]; - if ( firing ) { - stack.push( args ); - } else { - fire( args ); - } - } - return this; - }, - // Call all the callbacks with the given arguments - fire: function() { - self.fireWith( this, arguments ); - return this; - }, - // To know if the callbacks have already been called at least once - fired: function() { - return !!fired; - } - }; - - return self; -}; - - -jQuery.extend({ - - Deferred: function( func ) { - var tuples = [ - // action, add listener, listener list, final state - [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ], - [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ], - [ "notify", "progress", jQuery.Callbacks("memory") ] - ], - state = "pending", - promise = { - state: function() { - return state; - }, - always: function() { - deferred.done( arguments ).fail( arguments ); - return this; - }, - then: function( /* fnDone, fnFail, fnProgress */ ) { - var fns = arguments; - return jQuery.Deferred(function( newDefer ) { - jQuery.each( tuples, function( i, tuple ) { - var fn = jQuery.isFunction( fns[ i ] ) && fns[ i ]; - // deferred[ done | fail | progress ] for forwarding actions to newDefer - deferred[ tuple[1] ](function() { - var returned = fn && fn.apply( this, arguments ); - if ( returned && jQuery.isFunction( returned.promise ) ) { - returned.promise() - .done( newDefer.resolve ) - .fail( newDefer.reject ) - .progress( newDefer.notify ); - } else { - newDefer[ tuple[ 0 ] + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments ); - } - }); - }); - fns = null; - }).promise(); - }, - // Get a promise for this deferred - // If obj is provided, the promise aspect is added to the object - promise: function( obj ) { - return obj != null ? jQuery.extend( obj, promise ) : promise; - } - }, - deferred = {}; - - // Keep pipe for back-compat - promise.pipe = promise.then; - - // Add list-specific methods - jQuery.each( tuples, function( i, tuple ) { - var list = tuple[ 2 ], - stateString = tuple[ 3 ]; - - // promise[ done | fail | progress ] = list.add - promise[ tuple[1] ] = list.add; - - // Handle state - if ( stateString ) { - list.add(function() { - // state = [ resolved | rejected ] - state = stateString; - - // [ reject_list | resolve_list ].disable; progress_list.lock - }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock ); - } - - // deferred[ resolve | reject | notify ] - deferred[ tuple[0] ] = function() { - deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments ); - return this; - }; - deferred[ tuple[0] + "With" ] = list.fireWith; - }); - - // Make the deferred a promise - promise.promise( deferred ); - - // Call given func if any - if ( func ) { - func.call( deferred, deferred ); - } - - // All done! - return deferred; - }, - - // Deferred helper - when: function( subordinate /* , ..., subordinateN */ ) { - var i = 0, - resolveValues = slice.call( arguments ), - length = resolveValues.length, - - // the count of uncompleted subordinates - remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0, - - // the master Deferred. If resolveValues consist of only a single Deferred, just use that. - deferred = remaining === 1 ? subordinate : jQuery.Deferred(), - - // Update function for both resolve and progress values - updateFunc = function( i, contexts, values ) { - return function( value ) { - contexts[ i ] = this; - values[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; - if ( values === progressValues ) { - deferred.notifyWith( contexts, values ); - - } else if ( !(--remaining) ) { - deferred.resolveWith( contexts, values ); - } - }; - }, - - progressValues, progressContexts, resolveContexts; - - // add listeners to Deferred subordinates; treat others as resolved - if ( length > 1 ) { - progressValues = new Array( length ); - progressContexts = new Array( length ); - resolveContexts = new Array( length ); - for ( ; i < length; i++ ) { - if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) { - resolveValues[ i ].promise() - .done( updateFunc( i, resolveContexts, resolveValues ) ) - .fail( deferred.reject ) - .progress( updateFunc( i, progressContexts, progressValues ) ); - } else { - --remaining; - } - } - } - - // if we're not waiting on anything, resolve the master - if ( !remaining ) { - deferred.resolveWith( resolveContexts, resolveValues ); - } - - return deferred.promise(); - } -}); - - -// The deferred used on DOM ready -var readyList; - -jQuery.fn.ready = function( fn ) { - // Add the callback - jQuery.ready.promise().done( fn ); - - return this; -}; - -jQuery.extend({ - // Is the DOM ready to be used? Set to true once it occurs. - isReady: false, - - // A counter to track how many items to wait for before - // the ready event fires. See #6781 - readyWait: 1, - - // Hold (or release) the ready event - holdReady: function( hold ) { - if ( hold ) { - jQuery.readyWait++; - } else { - jQuery.ready( true ); - } - }, - - // Handle when the DOM is ready - ready: function( wait ) { - - // Abort if there are pending holds or we're already ready - if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { - return; - } - - // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). - if ( !document.body ) { - return setTimeout( jQuery.ready ); - } - - // Remember that the DOM is ready - jQuery.isReady = true; - - // If a normal DOM Ready event fired, decrement, and wait if need be - if ( wait !== true && --jQuery.readyWait > 0 ) { - return; - } - - // If there are functions bound, to execute - readyList.resolveWith( document, [ jQuery ] ); - - // Trigger any bound ready events - if ( jQuery.fn.triggerHandler ) { - jQuery( document ).triggerHandler( "ready" ); - jQuery( document ).off( "ready" ); - } - } -}); - -/** - * Clean-up method for dom ready events - */ -function detach() { - if ( document.addEventListener ) { - document.removeEventListener( "DOMContentLoaded", completed, false ); - window.removeEventListener( "load", completed, false ); - - } else { - document.detachEvent( "onreadystatechange", completed ); - window.detachEvent( "onload", completed ); - } -} - -/** - * The ready event handler and self cleanup method - */ -function completed() { - // readyState === "complete" is good enough for us to call the dom ready in oldIE - if ( document.addEventListener || event.type === "load" || document.readyState === "complete" ) { - detach(); - jQuery.ready(); - } -} - -jQuery.ready.promise = function( obj ) { - if ( !readyList ) { - - readyList = jQuery.Deferred(); - - // Catch cases where $(document).ready() is called after the browser event has already occurred. - // we once tried to use readyState "interactive" here, but it caused issues like the one - // discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15 - if ( document.readyState === "complete" ) { - // Handle it asynchronously to allow scripts the opportunity to delay ready - setTimeout( jQuery.ready ); - - // Standards-based browsers support DOMContentLoaded - } else if ( document.addEventListener ) { - // Use the handy event callback - document.addEventListener( "DOMContentLoaded", completed, false ); - - // A fallback to window.onload, that will always work - window.addEventListener( "load", completed, false ); - - // If IE event model is used - } else { - // Ensure firing before onload, maybe late but safe also for iframes - document.attachEvent( "onreadystatechange", completed ); - - // A fallback to window.onload, that will always work - window.attachEvent( "onload", completed ); - - // If IE and not a frame - // continually check to see if the document is ready - var top = false; - - try { - top = window.frameElement == null && document.documentElement; - } catch(e) {} - - if ( top && top.doScroll ) { - (function doScrollCheck() { - if ( !jQuery.isReady ) { - - try { - // Use the trick by Diego Perini - // http://javascript.nwbox.com/IEContentLoaded/ - top.doScroll("left"); - } catch(e) { - return setTimeout( doScrollCheck, 50 ); - } - - // detach all dom ready events - detach(); - - // and execute any waiting functions - jQuery.ready(); - } - })(); - } - } - } - return readyList.promise( obj ); -}; - - -var strundefined = typeof undefined; - - - -// Support: IE<9 -// Iteration over object's inherited properties before its own -var i; -for ( i in jQuery( support ) ) { - break; -} -support.ownLast = i !== "0"; - -// Note: most support tests are defined in their respective modules. -// false until the test is run -support.inlineBlockNeedsLayout = false; - -// Execute ASAP in case we need to set body.style.zoom -jQuery(function() { - // Minified: var a,b,c,d - var val, div, body, container; - - body = document.getElementsByTagName( "body" )[ 0 ]; - if ( !body || !body.style ) { - // Return for frameset docs that don't have a body - return; - } - - // Setup - div = document.createElement( "div" ); - container = document.createElement( "div" ); - container.style.cssText = "position:absolute;border:0;width:0;height:0;top:0;left:-9999px"; - body.appendChild( container ).appendChild( div ); - - if ( typeof div.style.zoom !== strundefined ) { - // Support: IE<8 - // Check if natively block-level elements act like inline-block - // elements when setting their display to 'inline' and giving - // them layout - div.style.cssText = "display:inline;margin:0;border:0;padding:1px;width:1px;zoom:1"; - - support.inlineBlockNeedsLayout = val = div.offsetWidth === 3; - if ( val ) { - // Prevent IE 6 from affecting layout for positioned elements #11048 - // Prevent IE from shrinking the body in IE 7 mode #12869 - // Support: IE<8 - body.style.zoom = 1; - } - } - - body.removeChild( container ); -}); - - - - -(function() { - var div = document.createElement( "div" ); - - // Execute the test only if not already executed in another module. - if (support.deleteExpando == null) { - // Support: IE<9 - support.deleteExpando = true; - try { - delete div.test; - } catch( e ) { - support.deleteExpando = false; - } - } - - // Null elements to avoid leaks in IE. - div = null; -})(); - - -/** - * Determines whether an object can have data - */ -jQuery.acceptData = function( elem ) { - var noData = jQuery.noData[ (elem.nodeName + " ").toLowerCase() ], - nodeType = +elem.nodeType || 1; - - // Do not set data on non-element DOM nodes because it will not be cleared (#8335). - return nodeType !== 1 && nodeType !== 9 ? - false : - - // Nodes accept data unless otherwise specified; rejection can be conditional - !noData || noData !== true && elem.getAttribute("classid") === noData; -}; - - -var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, - rmultiDash = /([A-Z])/g; - -function dataAttr( elem, key, data ) { - // If nothing was found internally, try to fetch any - // data from the HTML5 data-* attribute - if ( data === undefined && elem.nodeType === 1 ) { - - var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase(); - - data = elem.getAttribute( name ); - - if ( typeof data === "string" ) { - try { - data = data === "true" ? true : - data === "false" ? false : - data === "null" ? null : - // Only convert to a number if it doesn't change the string - +data + "" === data ? +data : - rbrace.test( data ) ? jQuery.parseJSON( data ) : - data; - } catch( e ) {} - - // Make sure we set the data so it isn't changed later - jQuery.data( elem, key, data ); - - } else { - data = undefined; - } - } - - return data; -} - -// checks a cache object for emptiness -function isEmptyDataObject( obj ) { - var name; - for ( name in obj ) { - - // if the public data object is empty, the private is still empty - if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) { - continue; - } - if ( name !== "toJSON" ) { - return false; - } - } - - return true; -} - -function internalData( elem, name, data, pvt /* Internal Use Only */ ) { - if ( !jQuery.acceptData( elem ) ) { - return; - } - - var ret, thisCache, - internalKey = jQuery.expando, - - // We have to handle DOM nodes and JS objects differently because IE6-7 - // can't GC object references properly across the DOM-JS boundary - isNode = elem.nodeType, - - // Only DOM nodes need the global jQuery cache; JS object data is - // attached directly to the object so GC can occur automatically - cache = isNode ? jQuery.cache : elem, - - // Only defining an ID for JS objects if its cache already exists allows - // the code to shortcut on the same path as a DOM node with no cache - id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey; - - // Avoid doing any more work than we need to when trying to get data on an - // object that has no data at all - if ( (!id || !cache[id] || (!pvt && !cache[id].data)) && data === undefined && typeof name === "string" ) { - return; - } - - if ( !id ) { - // Only DOM nodes need a new unique ID for each element since their data - // ends up in the global cache - if ( isNode ) { - id = elem[ internalKey ] = deletedIds.pop() || jQuery.guid++; - } else { - id = internalKey; - } - } - - if ( !cache[ id ] ) { - // Avoid exposing jQuery metadata on plain JS objects when the object - // is serialized using JSON.stringify - cache[ id ] = isNode ? {} : { toJSON: jQuery.noop }; - } - - // An object can be passed to jQuery.data instead of a key/value pair; this gets - // shallow copied over onto the existing cache - if ( typeof name === "object" || typeof name === "function" ) { - if ( pvt ) { - cache[ id ] = jQuery.extend( cache[ id ], name ); - } else { - cache[ id ].data = jQuery.extend( cache[ id ].data, name ); - } - } - - thisCache = cache[ id ]; - - // jQuery data() is stored in a separate object inside the object's internal data - // cache in order to avoid key collisions between internal data and user-defined - // data. - if ( !pvt ) { - if ( !thisCache.data ) { - thisCache.data = {}; - } - - thisCache = thisCache.data; - } - - if ( data !== undefined ) { - thisCache[ jQuery.camelCase( name ) ] = data; - } - - // Check for both converted-to-camel and non-converted data property names - // If a data property was specified - if ( typeof name === "string" ) { - - // First Try to find as-is property data - ret = thisCache[ name ]; - - // Test for null|undefined property data - if ( ret == null ) { - - // Try to find the camelCased property - ret = thisCache[ jQuery.camelCase( name ) ]; - } - } else { - ret = thisCache; - } - - return ret; -} - -function internalRemoveData( elem, name, pvt ) { - if ( !jQuery.acceptData( elem ) ) { - return; - } - - var thisCache, i, - isNode = elem.nodeType, - - // See jQuery.data for more information - cache = isNode ? jQuery.cache : elem, - id = isNode ? elem[ jQuery.expando ] : jQuery.expando; - - // If there is already no cache entry for this object, there is no - // purpose in continuing - if ( !cache[ id ] ) { - return; - } - - if ( name ) { - - thisCache = pvt ? cache[ id ] : cache[ id ].data; - - if ( thisCache ) { - - // Support array or space separated string names for data keys - if ( !jQuery.isArray( name ) ) { - - // try the string as a key before any manipulation - if ( name in thisCache ) { - name = [ name ]; - } else { - - // split the camel cased version by spaces unless a key with the spaces exists - name = jQuery.camelCase( name ); - if ( name in thisCache ) { - name = [ name ]; - } else { - name = name.split(" "); - } - } - } else { - // If "name" is an array of keys... - // When data is initially created, via ("key", "val") signature, - // keys will be converted to camelCase. - // Since there is no way to tell _how_ a key was added, remove - // both plain key and camelCase key. #12786 - // This will only penalize the array argument path. - name = name.concat( jQuery.map( name, jQuery.camelCase ) ); - } - - i = name.length; - while ( i-- ) { - delete thisCache[ name[i] ]; - } - - // If there is no data left in the cache, we want to continue - // and let the cache object itself get destroyed - if ( pvt ? !isEmptyDataObject(thisCache) : !jQuery.isEmptyObject(thisCache) ) { - return; - } - } - } - - // See jQuery.data for more information - if ( !pvt ) { - delete cache[ id ].data; - - // Don't destroy the parent cache unless the internal data object - // had been the only thing left in it - if ( !isEmptyDataObject( cache[ id ] ) ) { - return; - } - } - - // Destroy the cache - if ( isNode ) { - jQuery.cleanData( [ elem ], true ); - - // Use delete when supported for expandos or `cache` is not a window per isWindow (#10080) - /* jshint eqeqeq: false */ - } else if ( support.deleteExpando || cache != cache.window ) { - /* jshint eqeqeq: true */ - delete cache[ id ]; - - // When all else fails, null - } else { - cache[ id ] = null; - } -} - -jQuery.extend({ - cache: {}, - - // The following elements (space-suffixed to avoid Object.prototype collisions) - // throw uncatchable exceptions if you attempt to set expando properties - noData: { - "applet ": true, - "embed ": true, - // ...but Flash objects (which have this classid) *can* handle expandos - "object ": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" - }, - - hasData: function( elem ) { - elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ]; - return !!elem && !isEmptyDataObject( elem ); - }, - - data: function( elem, name, data ) { - return internalData( elem, name, data ); - }, - - removeData: function( elem, name ) { - return internalRemoveData( elem, name ); - }, - - // For internal use only. - _data: function( elem, name, data ) { - return internalData( elem, name, data, true ); - }, - - _removeData: function( elem, name ) { - return internalRemoveData( elem, name, true ); - } -}); - -jQuery.fn.extend({ - data: function( key, value ) { - var i, name, data, - elem = this[0], - attrs = elem && elem.attributes; - - // Special expections of .data basically thwart jQuery.access, - // so implement the relevant behavior ourselves - - // Gets all values - if ( key === undefined ) { - if ( this.length ) { - data = jQuery.data( elem ); - - if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) { - i = attrs.length; - while ( i-- ) { - - // Support: IE11+ - // The attrs elements can be null (#14894) - if ( attrs[ i ] ) { - name = attrs[ i ].name; - if ( name.indexOf( "data-" ) === 0 ) { - name = jQuery.camelCase( name.slice(5) ); - dataAttr( elem, name, data[ name ] ); - } - } - } - jQuery._data( elem, "parsedAttrs", true ); - } - } - - return data; - } - - // Sets multiple values - if ( typeof key === "object" ) { - return this.each(function() { - jQuery.data( this, key ); - }); - } - - return arguments.length > 1 ? - - // Sets one value - this.each(function() { - jQuery.data( this, key, value ); - }) : - - // Gets one value - // Try to fetch any internally stored data first - elem ? dataAttr( elem, key, jQuery.data( elem, key ) ) : undefined; - }, - - removeData: function( key ) { - return this.each(function() { - jQuery.removeData( this, key ); - }); - } -}); - - -jQuery.extend({ - queue: function( elem, type, data ) { - var queue; - - if ( elem ) { - type = ( type || "fx" ) + "queue"; - queue = jQuery._data( elem, type ); - - // Speed up dequeue by getting out quickly if this is just a lookup - if ( data ) { - if ( !queue || jQuery.isArray(data) ) { - queue = jQuery._data( elem, type, jQuery.makeArray(data) ); - } else { - queue.push( data ); - } - } - return queue || []; - } - }, - - dequeue: function( elem, type ) { - type = type || "fx"; - - var queue = jQuery.queue( elem, type ), - startLength = queue.length, - fn = queue.shift(), - hooks = jQuery._queueHooks( elem, type ), - next = function() { - jQuery.dequeue( elem, type ); - }; - - // If the fx queue is dequeued, always remove the progress sentinel - if ( fn === "inprogress" ) { - fn = queue.shift(); - startLength--; - } - - if ( fn ) { - - // Add a progress sentinel to prevent the fx queue from being - // automatically dequeued - if ( type === "fx" ) { - queue.unshift( "inprogress" ); - } - - // clear up the last queue stop function - delete hooks.stop; - fn.call( elem, next, hooks ); - } - - if ( !startLength && hooks ) { - hooks.empty.fire(); - } - }, - - // not intended for public consumption - generates a queueHooks object, or returns the current one - _queueHooks: function( elem, type ) { - var key = type + "queueHooks"; - return jQuery._data( elem, key ) || jQuery._data( elem, key, { - empty: jQuery.Callbacks("once memory").add(function() { - jQuery._removeData( elem, type + "queue" ); - jQuery._removeData( elem, key ); - }) - }); - } -}); - -jQuery.fn.extend({ - queue: function( type, data ) { - var setter = 2; - - if ( typeof type !== "string" ) { - data = type; - type = "fx"; - setter--; - } - - if ( arguments.length < setter ) { - return jQuery.queue( this[0], type ); - } - - return data === undefined ? - this : - this.each(function() { - var queue = jQuery.queue( this, type, data ); - - // ensure a hooks for this queue - jQuery._queueHooks( this, type ); - - if ( type === "fx" && queue[0] !== "inprogress" ) { - jQuery.dequeue( this, type ); - } - }); - }, - dequeue: function( type ) { - return this.each(function() { - jQuery.dequeue( this, type ); - }); - }, - clearQueue: function( type ) { - return this.queue( type || "fx", [] ); - }, - // Get a promise resolved when queues of a certain type - // are emptied (fx is the type by default) - promise: function( type, obj ) { - var tmp, - count = 1, - defer = jQuery.Deferred(), - elements = this, - i = this.length, - resolve = function() { - if ( !( --count ) ) { - defer.resolveWith( elements, [ elements ] ); - } - }; - - if ( typeof type !== "string" ) { - obj = type; - type = undefined; - } - type = type || "fx"; - - while ( i-- ) { - tmp = jQuery._data( elements[ i ], type + "queueHooks" ); - if ( tmp && tmp.empty ) { - count++; - tmp.empty.add( resolve ); - } - } - resolve(); - return defer.promise( obj ); - } -}); -var pnum = (/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/).source; - -var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; - -var isHidden = function( elem, el ) { - // isHidden might be called from jQuery#filter function; - // in that case, element will be second argument - elem = el || elem; - return jQuery.css( elem, "display" ) === "none" || !jQuery.contains( elem.ownerDocument, elem ); - }; - - - -// Multifunctional method to get and set values of a collection -// The value/s can optionally be executed if it's a function -var access = jQuery.access = function( elems, fn, key, value, chainable, emptyGet, raw ) { - var i = 0, - length = elems.length, - bulk = key == null; - - // Sets many values - if ( jQuery.type( key ) === "object" ) { - chainable = true; - for ( i in key ) { - jQuery.access( elems, fn, i, key[i], true, emptyGet, raw ); - } - - // Sets one value - } else if ( value !== undefined ) { - chainable = true; - - if ( !jQuery.isFunction( value ) ) { - raw = true; - } - - if ( bulk ) { - // Bulk operations run against the entire set - if ( raw ) { - fn.call( elems, value ); - fn = null; - - // ...except when executing function values - } else { - bulk = fn; - fn = function( elem, key, value ) { - return bulk.call( jQuery( elem ), value ); - }; - } - } - - if ( fn ) { - for ( ; i < length; i++ ) { - fn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) ); - } - } - } - - return chainable ? - elems : - - // Gets - bulk ? - fn.call( elems ) : - length ? fn( elems[0], key ) : emptyGet; -}; -var rcheckableType = (/^(?:checkbox|radio)$/i); - - - -(function() { - // Minified: var a,b,c - var input = document.createElement( "input" ), - div = document.createElement( "div" ), - fragment = document.createDocumentFragment(); - - // Setup - div.innerHTML = "
a"; - - // IE strips leading whitespace when .innerHTML is used - support.leadingWhitespace = div.firstChild.nodeType === 3; - - // Make sure that tbody elements aren't automatically inserted - // IE will insert them into empty tables - support.tbody = !div.getElementsByTagName( "tbody" ).length; - - // Make sure that link elements get serialized correctly by innerHTML - // This requires a wrapper element in IE - support.htmlSerialize = !!div.getElementsByTagName( "link" ).length; - - // Makes sure cloning an html5 element does not cause problems - // Where outerHTML is undefined, this still works - support.html5Clone = - document.createElement( "nav" ).cloneNode( true ).outerHTML !== "<:nav>"; - - // Check if a disconnected checkbox will retain its checked - // value of true after appended to the DOM (IE6/7) - input.type = "checkbox"; - input.checked = true; - fragment.appendChild( input ); - support.appendChecked = input.checked; - - // Make sure textarea (and checkbox) defaultValue is properly cloned - // Support: IE6-IE11+ - div.innerHTML = ""; - support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; - - // #11217 - WebKit loses check when the name is after the checked attribute - fragment.appendChild( div ); - div.innerHTML = ""; - - // Support: Safari 5.1, iOS 5.1, Android 4.x, Android 2.3 - // old WebKit doesn't clone checked state correctly in fragments - support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; - - // Support: IE<9 - // Opera does not clone events (and typeof div.attachEvent === undefined). - // IE9-10 clones events bound via attachEvent, but they don't trigger with .click() - support.noCloneEvent = true; - if ( div.attachEvent ) { - div.attachEvent( "onclick", function() { - support.noCloneEvent = false; - }); - - div.cloneNode( true ).click(); - } - - // Execute the test only if not already executed in another module. - if (support.deleteExpando == null) { - // Support: IE<9 - support.deleteExpando = true; - try { - delete div.test; - } catch( e ) { - support.deleteExpando = false; - } - } -})(); - - -(function() { - var i, eventName, - div = document.createElement( "div" ); - - // Support: IE<9 (lack submit/change bubble), Firefox 23+ (lack focusin event) - for ( i in { submit: true, change: true, focusin: true }) { - eventName = "on" + i; - - if ( !(support[ i + "Bubbles" ] = eventName in window) ) { - // Beware of CSP restrictions (https://developer.mozilla.org/en/Security/CSP) - div.setAttribute( eventName, "t" ); - support[ i + "Bubbles" ] = div.attributes[ eventName ].expando === false; - } - } - - // Null elements to avoid leaks in IE. - div = null; -})(); - - -var rformElems = /^(?:input|select|textarea)$/i, - rkeyEvent = /^key/, - rmouseEvent = /^(?:mouse|pointer|contextmenu)|click/, - rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, - rtypenamespace = /^([^.]*)(?:\.(.+)|)$/; - -function returnTrue() { - return true; -} - -function returnFalse() { - return false; -} - -function safeActiveElement() { - try { - return document.activeElement; - } catch ( err ) { } -} - -/* - * Helper functions for managing events -- not part of the public interface. - * Props to Dean Edwards' addEvent library for many of the ideas. - */ -jQuery.event = { - - global: {}, - - add: function( elem, types, handler, data, selector ) { - var tmp, events, t, handleObjIn, - special, eventHandle, handleObj, - handlers, type, namespaces, origType, - elemData = jQuery._data( elem ); - - // Don't attach events to noData or text/comment nodes (but allow plain objects) - if ( !elemData ) { - return; - } - - // Caller can pass in an object of custom data in lieu of the handler - if ( handler.handler ) { - handleObjIn = handler; - handler = handleObjIn.handler; - selector = handleObjIn.selector; - } - - // Make sure that the handler has a unique ID, used to find/remove it later - if ( !handler.guid ) { - handler.guid = jQuery.guid++; - } - - // Init the element's event structure and main handler, if this is the first - if ( !(events = elemData.events) ) { - events = elemData.events = {}; - } - if ( !(eventHandle = elemData.handle) ) { - eventHandle = elemData.handle = function( e ) { - // Discard the second event of a jQuery.event.trigger() and - // when an event is called after a page has unloaded - return typeof jQuery !== strundefined && (!e || jQuery.event.triggered !== e.type) ? - jQuery.event.dispatch.apply( eventHandle.elem, arguments ) : - undefined; - }; - // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events - eventHandle.elem = elem; - } - - // Handle multiple events separated by a space - types = ( types || "" ).match( rnotwhite ) || [ "" ]; - t = types.length; - while ( t-- ) { - tmp = rtypenamespace.exec( types[t] ) || []; - type = origType = tmp[1]; - namespaces = ( tmp[2] || "" ).split( "." ).sort(); - - // There *must* be a type, no attaching namespace-only handlers - if ( !type ) { - continue; - } - - // If event changes its type, use the special event handlers for the changed type - special = jQuery.event.special[ type ] || {}; - - // If selector defined, determine special event api type, otherwise given type - type = ( selector ? special.delegateType : special.bindType ) || type; - - // Update special based on newly reset type - special = jQuery.event.special[ type ] || {}; - - // handleObj is passed to all event handlers - handleObj = jQuery.extend({ - type: type, - origType: origType, - data: data, - handler: handler, - guid: handler.guid, - selector: selector, - needsContext: selector && jQuery.expr.match.needsContext.test( selector ), - namespace: namespaces.join(".") - }, handleObjIn ); - - // Init the event handler queue if we're the first - if ( !(handlers = events[ type ]) ) { - handlers = events[ type ] = []; - handlers.delegateCount = 0; - - // Only use addEventListener/attachEvent if the special events handler returns false - if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { - // Bind the global event handler to the element - if ( elem.addEventListener ) { - elem.addEventListener( type, eventHandle, false ); - - } else if ( elem.attachEvent ) { - elem.attachEvent( "on" + type, eventHandle ); - } - } - } - - if ( special.add ) { - special.add.call( elem, handleObj ); - - if ( !handleObj.handler.guid ) { - handleObj.handler.guid = handler.guid; - } - } - - // Add to the element's handler list, delegates in front - if ( selector ) { - handlers.splice( handlers.delegateCount++, 0, handleObj ); - } else { - handlers.push( handleObj ); - } - - // Keep track of which events have ever been used, for event optimization - jQuery.event.global[ type ] = true; - } - - // Nullify elem to prevent memory leaks in IE - elem = null; - }, - - // Detach an event or set of events from an element - remove: function( elem, types, handler, selector, mappedTypes ) { - var j, handleObj, tmp, - origCount, t, events, - special, handlers, type, - namespaces, origType, - elemData = jQuery.hasData( elem ) && jQuery._data( elem ); - - if ( !elemData || !(events = elemData.events) ) { - return; - } - - // Once for each type.namespace in types; type may be omitted - types = ( types || "" ).match( rnotwhite ) || [ "" ]; - t = types.length; - while ( t-- ) { - tmp = rtypenamespace.exec( types[t] ) || []; - type = origType = tmp[1]; - namespaces = ( tmp[2] || "" ).split( "." ).sort(); - - // Unbind all events (on this namespace, if provided) for the element - if ( !type ) { - for ( type in events ) { - jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); - } - continue; - } - - special = jQuery.event.special[ type ] || {}; - type = ( selector ? special.delegateType : special.bindType ) || type; - handlers = events[ type ] || []; - tmp = tmp[2] && new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ); - - // Remove matching events - origCount = j = handlers.length; - while ( j-- ) { - handleObj = handlers[ j ]; - - if ( ( mappedTypes || origType === handleObj.origType ) && - ( !handler || handler.guid === handleObj.guid ) && - ( !tmp || tmp.test( handleObj.namespace ) ) && - ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) { - handlers.splice( j, 1 ); - - if ( handleObj.selector ) { - handlers.delegateCount--; - } - if ( special.remove ) { - special.remove.call( elem, handleObj ); - } - } - } - - // Remove generic event handler if we removed something and no more handlers exist - // (avoids potential for endless recursion during removal of special event handlers) - if ( origCount && !handlers.length ) { - if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) { - jQuery.removeEvent( elem, type, elemData.handle ); - } - - delete events[ type ]; - } - } - - // Remove the expando if it's no longer used - if ( jQuery.isEmptyObject( events ) ) { - delete elemData.handle; - - // removeData also checks for emptiness and clears the expando if empty - // so use it instead of delete - jQuery._removeData( elem, "events" ); - } - }, - - trigger: function( event, data, elem, onlyHandlers ) { - var handle, ontype, cur, - bubbleType, special, tmp, i, - eventPath = [ elem || document ], - type = hasOwn.call( event, "type" ) ? event.type : event, - namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split(".") : []; - - cur = tmp = elem = elem || document; - - // Don't do events on text and comment nodes - if ( elem.nodeType === 3 || elem.nodeType === 8 ) { - return; - } - - // focus/blur morphs to focusin/out; ensure we're not firing them right now - if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { - return; - } - - if ( type.indexOf(".") >= 0 ) { - // Namespaced trigger; create a regexp to match event type in handle() - namespaces = type.split("."); - type = namespaces.shift(); - namespaces.sort(); - } - ontype = type.indexOf(":") < 0 && "on" + type; - - // Caller can pass in a jQuery.Event object, Object, or just an event type string - event = event[ jQuery.expando ] ? - event : - new jQuery.Event( type, typeof event === "object" && event ); - - // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) - event.isTrigger = onlyHandlers ? 2 : 3; - event.namespace = namespaces.join("."); - event.namespace_re = event.namespace ? - new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ) : - null; - - // Clean up the event in case it is being reused - event.result = undefined; - if ( !event.target ) { - event.target = elem; - } - - // Clone any incoming data and prepend the event, creating the handler arg list - data = data == null ? - [ event ] : - jQuery.makeArray( data, [ event ] ); - - // Allow special events to draw outside the lines - special = jQuery.event.special[ type ] || {}; - if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { - return; - } - - // Determine event propagation path in advance, per W3C events spec (#9951) - // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) - if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { - - bubbleType = special.delegateType || type; - if ( !rfocusMorph.test( bubbleType + type ) ) { - cur = cur.parentNode; - } - for ( ; cur; cur = cur.parentNode ) { - eventPath.push( cur ); - tmp = cur; - } - - // Only add window if we got to document (e.g., not plain obj or detached DOM) - if ( tmp === (elem.ownerDocument || document) ) { - eventPath.push( tmp.defaultView || tmp.parentWindow || window ); - } - } - - // Fire handlers on the event path - i = 0; - while ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) { - - event.type = i > 1 ? - bubbleType : - special.bindType || type; - - // jQuery handler - handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" ); - if ( handle ) { - handle.apply( cur, data ); - } - - // Native handler - handle = ontype && cur[ ontype ]; - if ( handle && handle.apply && jQuery.acceptData( cur ) ) { - event.result = handle.apply( cur, data ); - if ( event.result === false ) { - event.preventDefault(); - } - } - } - event.type = type; - - // If nobody prevented the default action, do it now - if ( !onlyHandlers && !event.isDefaultPrevented() ) { - - if ( (!special._default || special._default.apply( eventPath.pop(), data ) === false) && - jQuery.acceptData( elem ) ) { - - // Call a native DOM method on the target with the same name name as the event. - // Can't use an .isFunction() check here because IE6/7 fails that test. - // Don't do default actions on window, that's where global variables be (#6170) - if ( ontype && elem[ type ] && !jQuery.isWindow( elem ) ) { - - // Don't re-trigger an onFOO event when we call its FOO() method - tmp = elem[ ontype ]; - - if ( tmp ) { - elem[ ontype ] = null; - } - - // Prevent re-triggering of the same event, since we already bubbled it above - jQuery.event.triggered = type; - try { - elem[ type ](); - } catch ( e ) { - // IE<9 dies on focus/blur to hidden element (#1486,#12518) - // only reproducible on winXP IE8 native, not IE9 in IE8 mode - } - jQuery.event.triggered = undefined; - - if ( tmp ) { - elem[ ontype ] = tmp; - } - } - } - } - - return event.result; - }, - - dispatch: function( event ) { - - // Make a writable jQuery.Event from the native event object - event = jQuery.event.fix( event ); - - var i, ret, handleObj, matched, j, - handlerQueue = [], - args = slice.call( arguments ), - handlers = ( jQuery._data( this, "events" ) || {} )[ event.type ] || [], - special = jQuery.event.special[ event.type ] || {}; - - // Use the fix-ed jQuery.Event rather than the (read-only) native event - args[0] = event; - event.delegateTarget = this; - - // Call the preDispatch hook for the mapped type, and let it bail if desired - if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { - return; - } - - // Determine handlers - handlerQueue = jQuery.event.handlers.call( this, event, handlers ); - - // Run delegates first; they may want to stop propagation beneath us - i = 0; - while ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) { - event.currentTarget = matched.elem; - - j = 0; - while ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) { - - // Triggered event must either 1) have no namespace, or - // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace). - if ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) { - - event.handleObj = handleObj; - event.data = handleObj.data; - - ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler ) - .apply( matched.elem, args ); - - if ( ret !== undefined ) { - if ( (event.result = ret) === false ) { - event.preventDefault(); - event.stopPropagation(); - } - } - } - } - } - - // Call the postDispatch hook for the mapped type - if ( special.postDispatch ) { - special.postDispatch.call( this, event ); - } - - return event.result; - }, - - handlers: function( event, handlers ) { - var sel, handleObj, matches, i, - handlerQueue = [], - delegateCount = handlers.delegateCount, - cur = event.target; - - // Find delegate handlers - // Black-hole SVG instance trees (#13180) - // Avoid non-left-click bubbling in Firefox (#3861) - if ( delegateCount && cur.nodeType && (!event.button || event.type !== "click") ) { - - /* jshint eqeqeq: false */ - for ( ; cur != this; cur = cur.parentNode || this ) { - /* jshint eqeqeq: true */ - - // Don't check non-elements (#13208) - // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) - if ( cur.nodeType === 1 && (cur.disabled !== true || event.type !== "click") ) { - matches = []; - for ( i = 0; i < delegateCount; i++ ) { - handleObj = handlers[ i ]; - - // Don't conflict with Object.prototype properties (#13203) - sel = handleObj.selector + " "; - - if ( matches[ sel ] === undefined ) { - matches[ sel ] = handleObj.needsContext ? - jQuery( sel, this ).index( cur ) >= 0 : - jQuery.find( sel, this, null, [ cur ] ).length; - } - if ( matches[ sel ] ) { - matches.push( handleObj ); - } - } - if ( matches.length ) { - handlerQueue.push({ elem: cur, handlers: matches }); - } - } - } - } - - // Add the remaining (directly-bound) handlers - if ( delegateCount < handlers.length ) { - handlerQueue.push({ elem: this, handlers: handlers.slice( delegateCount ) }); - } - - return handlerQueue; - }, - - fix: function( event ) { - if ( event[ jQuery.expando ] ) { - return event; - } - - // Create a writable copy of the event object and normalize some properties - var i, prop, copy, - type = event.type, - originalEvent = event, - fixHook = this.fixHooks[ type ]; - - if ( !fixHook ) { - this.fixHooks[ type ] = fixHook = - rmouseEvent.test( type ) ? this.mouseHooks : - rkeyEvent.test( type ) ? this.keyHooks : - {}; - } - copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props; - - event = new jQuery.Event( originalEvent ); - - i = copy.length; - while ( i-- ) { - prop = copy[ i ]; - event[ prop ] = originalEvent[ prop ]; - } - - // Support: IE<9 - // Fix target property (#1925) - if ( !event.target ) { - event.target = originalEvent.srcElement || document; - } - - // Support: Chrome 23+, Safari? - // Target should not be a text node (#504, #13143) - if ( event.target.nodeType === 3 ) { - event.target = event.target.parentNode; - } - - // Support: IE<9 - // For mouse/key events, metaKey==false if it's undefined (#3368, #11328) - event.metaKey = !!event.metaKey; - - return fixHook.filter ? fixHook.filter( event, originalEvent ) : event; - }, - - // Includes some event props shared by KeyEvent and MouseEvent - props: "altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "), - - fixHooks: {}, - - keyHooks: { - props: "char charCode key keyCode".split(" "), - filter: function( event, original ) { - - // Add which for key events - if ( event.which == null ) { - event.which = original.charCode != null ? original.charCode : original.keyCode; - } - - return event; - } - }, - - mouseHooks: { - props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "), - filter: function( event, original ) { - var body, eventDoc, doc, - button = original.button, - fromElement = original.fromElement; - - // Calculate pageX/Y if missing and clientX/Y available - if ( event.pageX == null && original.clientX != null ) { - eventDoc = event.target.ownerDocument || document; - doc = eventDoc.documentElement; - body = eventDoc.body; - - event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 ); - event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 ); - } - - // Add relatedTarget, if necessary - if ( !event.relatedTarget && fromElement ) { - event.relatedTarget = fromElement === event.target ? original.toElement : fromElement; - } - - // Add which for click: 1 === left; 2 === middle; 3 === right - // Note: button is not normalized, so don't use it - if ( !event.which && button !== undefined ) { - event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) ); - } - - return event; - } - }, - - special: { - load: { - // Prevent triggered image.load events from bubbling to window.load - noBubble: true - }, - focus: { - // Fire native event if possible so blur/focus sequence is correct - trigger: function() { - if ( this !== safeActiveElement() && this.focus ) { - try { - this.focus(); - return false; - } catch ( e ) { - // Support: IE<9 - // If we error on focus to hidden element (#1486, #12518), - // let .trigger() run the handlers - } - } - }, - delegateType: "focusin" - }, - blur: { - trigger: function() { - if ( this === safeActiveElement() && this.blur ) { - this.blur(); - return false; - } - }, - delegateType: "focusout" - }, - click: { - // For checkbox, fire native event so checked state will be right - trigger: function() { - if ( jQuery.nodeName( this, "input" ) && this.type === "checkbox" && this.click ) { - this.click(); - return false; - } - }, - - // For cross-browser consistency, don't fire native .click() on links - _default: function( event ) { - return jQuery.nodeName( event.target, "a" ); - } - }, - - beforeunload: { - postDispatch: function( event ) { - - // Support: Firefox 20+ - // Firefox doesn't alert if the returnValue field is not set. - if ( event.result !== undefined && event.originalEvent ) { - event.originalEvent.returnValue = event.result; - } - } - } - }, - - simulate: function( type, elem, event, bubble ) { - // Piggyback on a donor event to simulate a different one. - // Fake originalEvent to avoid donor's stopPropagation, but if the - // simulated event prevents default then we do the same on the donor. - var e = jQuery.extend( - new jQuery.Event(), - event, - { - type: type, - isSimulated: true, - originalEvent: {} - } - ); - if ( bubble ) { - jQuery.event.trigger( e, null, elem ); - } else { - jQuery.event.dispatch.call( elem, e ); - } - if ( e.isDefaultPrevented() ) { - event.preventDefault(); - } - } -}; - -jQuery.removeEvent = document.removeEventListener ? - function( elem, type, handle ) { - if ( elem.removeEventListener ) { - elem.removeEventListener( type, handle, false ); - } - } : - function( elem, type, handle ) { - var name = "on" + type; - - if ( elem.detachEvent ) { - - // #8545, #7054, preventing memory leaks for custom events in IE6-8 - // detachEvent needed property on element, by name of that event, to properly expose it to GC - if ( typeof elem[ name ] === strundefined ) { - elem[ name ] = null; - } - - elem.detachEvent( name, handle ); - } - }; - -jQuery.Event = function( src, props ) { - // Allow instantiation without the 'new' keyword - if ( !(this instanceof jQuery.Event) ) { - return new jQuery.Event( src, props ); - } - - // Event object - if ( src && src.type ) { - this.originalEvent = src; - this.type = src.type; - - // Events bubbling up the document may have been marked as prevented - // by a handler lower down the tree; reflect the correct value. - this.isDefaultPrevented = src.defaultPrevented || - src.defaultPrevented === undefined && - // Support: IE < 9, Android < 4.0 - src.returnValue === false ? - returnTrue : - returnFalse; - - // Event type - } else { - this.type = src; - } - - // Put explicitly provided properties onto the event object - if ( props ) { - jQuery.extend( this, props ); - } - - // Create a timestamp if incoming event doesn't have one - this.timeStamp = src && src.timeStamp || jQuery.now(); - - // Mark it as fixed - this[ jQuery.expando ] = true; -}; - -// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding -// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html -jQuery.Event.prototype = { - isDefaultPrevented: returnFalse, - isPropagationStopped: returnFalse, - isImmediatePropagationStopped: returnFalse, - - preventDefault: function() { - var e = this.originalEvent; - - this.isDefaultPrevented = returnTrue; - if ( !e ) { - return; - } - - // If preventDefault exists, run it on the original event - if ( e.preventDefault ) { - e.preventDefault(); - - // Support: IE - // Otherwise set the returnValue property of the original event to false - } else { - e.returnValue = false; - } - }, - stopPropagation: function() { - var e = this.originalEvent; - - this.isPropagationStopped = returnTrue; - if ( !e ) { - return; - } - // If stopPropagation exists, run it on the original event - if ( e.stopPropagation ) { - e.stopPropagation(); - } - - // Support: IE - // Set the cancelBubble property of the original event to true - e.cancelBubble = true; - }, - stopImmediatePropagation: function() { - var e = this.originalEvent; - - this.isImmediatePropagationStopped = returnTrue; - - if ( e && e.stopImmediatePropagation ) { - e.stopImmediatePropagation(); - } - - this.stopPropagation(); - } -}; - -// Create mouseenter/leave events using mouseover/out and event-time checks -jQuery.each({ - mouseenter: "mouseover", - mouseleave: "mouseout", - pointerenter: "pointerover", - pointerleave: "pointerout" -}, function( orig, fix ) { - jQuery.event.special[ orig ] = { - delegateType: fix, - bindType: fix, - - handle: function( event ) { - var ret, - target = this, - related = event.relatedTarget, - handleObj = event.handleObj; - - // For mousenter/leave call the handler if related is outside the target. - // NB: No relatedTarget if the mouse left/entered the browser window - if ( !related || (related !== target && !jQuery.contains( target, related )) ) { - event.type = handleObj.origType; - ret = handleObj.handler.apply( this, arguments ); - event.type = fix; - } - return ret; - } - }; -}); - -// IE submit delegation -if ( !support.submitBubbles ) { - - jQuery.event.special.submit = { - setup: function() { - // Only need this for delegated form submit events - if ( jQuery.nodeName( this, "form" ) ) { - return false; - } - - // Lazy-add a submit handler when a descendant form may potentially be submitted - jQuery.event.add( this, "click._submit keypress._submit", function( e ) { - // Node name check avoids a VML-related crash in IE (#9807) - var elem = e.target, - form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined; - if ( form && !jQuery._data( form, "submitBubbles" ) ) { - jQuery.event.add( form, "submit._submit", function( event ) { - event._submit_bubble = true; - }); - jQuery._data( form, "submitBubbles", true ); - } - }); - // return undefined since we don't need an event listener - }, - - postDispatch: function( event ) { - // If form was submitted by the user, bubble the event up the tree - if ( event._submit_bubble ) { - delete event._submit_bubble; - if ( this.parentNode && !event.isTrigger ) { - jQuery.event.simulate( "submit", this.parentNode, event, true ); - } - } - }, - - teardown: function() { - // Only need this for delegated form submit events - if ( jQuery.nodeName( this, "form" ) ) { - return false; - } - - // Remove delegated handlers; cleanData eventually reaps submit handlers attached above - jQuery.event.remove( this, "._submit" ); - } - }; -} - -// IE change delegation and checkbox/radio fix -if ( !support.changeBubbles ) { - - jQuery.event.special.change = { - - setup: function() { - - if ( rformElems.test( this.nodeName ) ) { - // IE doesn't fire change on a check/radio until blur; trigger it on click - // after a propertychange. Eat the blur-change in special.change.handle. - // This still fires onchange a second time for check/radio after blur. - if ( this.type === "checkbox" || this.type === "radio" ) { - jQuery.event.add( this, "propertychange._change", function( event ) { - if ( event.originalEvent.propertyName === "checked" ) { - this._just_changed = true; - } - }); - jQuery.event.add( this, "click._change", function( event ) { - if ( this._just_changed && !event.isTrigger ) { - this._just_changed = false; - } - // Allow triggered, simulated change events (#11500) - jQuery.event.simulate( "change", this, event, true ); - }); - } - return false; - } - // Delegated event; lazy-add a change handler on descendant inputs - jQuery.event.add( this, "beforeactivate._change", function( e ) { - var elem = e.target; - - if ( rformElems.test( elem.nodeName ) && !jQuery._data( elem, "changeBubbles" ) ) { - jQuery.event.add( elem, "change._change", function( event ) { - if ( this.parentNode && !event.isSimulated && !event.isTrigger ) { - jQuery.event.simulate( "change", this.parentNode, event, true ); - } - }); - jQuery._data( elem, "changeBubbles", true ); - } - }); - }, - - handle: function( event ) { - var elem = event.target; - - // Swallow native change events from checkbox/radio, we already triggered them above - if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) { - return event.handleObj.handler.apply( this, arguments ); - } - }, - - teardown: function() { - jQuery.event.remove( this, "._change" ); - - return !rformElems.test( this.nodeName ); - } - }; -} - -// Create "bubbling" focus and blur events -if ( !support.focusinBubbles ) { - jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { - - // Attach a single capturing handler on the document while someone wants focusin/focusout - var handler = function( event ) { - jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true ); - }; - - jQuery.event.special[ fix ] = { - setup: function() { - var doc = this.ownerDocument || this, - attaches = jQuery._data( doc, fix ); - - if ( !attaches ) { - doc.addEventListener( orig, handler, true ); - } - jQuery._data( doc, fix, ( attaches || 0 ) + 1 ); - }, - teardown: function() { - var doc = this.ownerDocument || this, - attaches = jQuery._data( doc, fix ) - 1; - - if ( !attaches ) { - doc.removeEventListener( orig, handler, true ); - jQuery._removeData( doc, fix ); - } else { - jQuery._data( doc, fix, attaches ); - } - } - }; - }); -} - -jQuery.fn.extend({ - - on: function( types, selector, data, fn, /*INTERNAL*/ one ) { - var type, origFn; - - // Types can be a map of types/handlers - if ( typeof types === "object" ) { - // ( types-Object, selector, data ) - if ( typeof selector !== "string" ) { - // ( types-Object, data ) - data = data || selector; - selector = undefined; - } - for ( type in types ) { - this.on( type, selector, data, types[ type ], one ); - } - return this; - } - - if ( data == null && fn == null ) { - // ( types, fn ) - fn = selector; - data = selector = undefined; - } else if ( fn == null ) { - if ( typeof selector === "string" ) { - // ( types, selector, fn ) - fn = data; - data = undefined; - } else { - // ( types, data, fn ) - fn = data; - data = selector; - selector = undefined; - } - } - if ( fn === false ) { - fn = returnFalse; - } else if ( !fn ) { - return this; - } - - if ( one === 1 ) { - origFn = fn; - fn = function( event ) { - // Can use an empty set, since event contains the info - jQuery().off( event ); - return origFn.apply( this, arguments ); - }; - // Use same guid so caller can remove using origFn - fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); - } - return this.each( function() { - jQuery.event.add( this, types, fn, data, selector ); - }); - }, - one: function( types, selector, data, fn ) { - return this.on( types, selector, data, fn, 1 ); - }, - off: function( types, selector, fn ) { - var handleObj, type; - if ( types && types.preventDefault && types.handleObj ) { - // ( event ) dispatched jQuery.Event - handleObj = types.handleObj; - jQuery( types.delegateTarget ).off( - handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType, - handleObj.selector, - handleObj.handler - ); - return this; - } - if ( typeof types === "object" ) { - // ( types-object [, selector] ) - for ( type in types ) { - this.off( type, selector, types[ type ] ); - } - return this; - } - if ( selector === false || typeof selector === "function" ) { - // ( types [, fn] ) - fn = selector; - selector = undefined; - } - if ( fn === false ) { - fn = returnFalse; - } - return this.each(function() { - jQuery.event.remove( this, types, fn, selector ); - }); - }, - - trigger: function( type, data ) { - return this.each(function() { - jQuery.event.trigger( type, data, this ); - }); - }, - triggerHandler: function( type, data ) { - var elem = this[0]; - if ( elem ) { - return jQuery.event.trigger( type, data, elem, true ); - } - } -}); - - -function createSafeFragment( document ) { - var list = nodeNames.split( "|" ), - safeFrag = document.createDocumentFragment(); - - if ( safeFrag.createElement ) { - while ( list.length ) { - safeFrag.createElement( - list.pop() - ); - } - } - return safeFrag; -} - -var nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|" + - "header|hgroup|mark|meter|nav|output|progress|section|summary|time|video", - rinlinejQuery = / jQuery\d+="(?:null|\d+)"/g, - rnoshimcache = new RegExp("<(?:" + nodeNames + ")[\\s/>]", "i"), - rleadingWhitespace = /^\s+/, - rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi, - rtagName = /<([\w:]+)/, - rtbody = /\s*$/g, - - // We have to close these tags to support XHTML (#13200) - wrapMap = { - option: [ 1, "" ], - legend: [ 1, "
", "
" ], - area: [ 1, "", "" ], - param: [ 1, "", "" ], - thead: [ 1, "", "
" ], - tr: [ 2, "", "
" ], - col: [ 2, "", "
" ], - td: [ 3, "", "
" ], - - // IE6-8 can't serialize link, script, style, or any html5 (NoScope) tags, - // unless wrapped in a div with non-breaking characters in front of it. - _default: support.htmlSerialize ? [ 0, "", "" ] : [ 1, "X
", "
" ] - }, - safeFragment = createSafeFragment( document ), - fragmentDiv = safeFragment.appendChild( document.createElement("div") ); - -wrapMap.optgroup = wrapMap.option; -wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; -wrapMap.th = wrapMap.td; - -function getAll( context, tag ) { - var elems, elem, - i = 0, - found = typeof context.getElementsByTagName !== strundefined ? context.getElementsByTagName( tag || "*" ) : - typeof context.querySelectorAll !== strundefined ? context.querySelectorAll( tag || "*" ) : - undefined; - - if ( !found ) { - for ( found = [], elems = context.childNodes || context; (elem = elems[i]) != null; i++ ) { - if ( !tag || jQuery.nodeName( elem, tag ) ) { - found.push( elem ); - } else { - jQuery.merge( found, getAll( elem, tag ) ); - } - } - } - - return tag === undefined || tag && jQuery.nodeName( context, tag ) ? - jQuery.merge( [ context ], found ) : - found; -} - -// Used in buildFragment, fixes the defaultChecked property -function fixDefaultChecked( elem ) { - if ( rcheckableType.test( elem.type ) ) { - elem.defaultChecked = elem.checked; - } -} - -// Support: IE<8 -// Manipulating tables requires a tbody -function manipulationTarget( elem, content ) { - return jQuery.nodeName( elem, "table" ) && - jQuery.nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ? - - elem.getElementsByTagName("tbody")[0] || - elem.appendChild( elem.ownerDocument.createElement("tbody") ) : - elem; -} - -// Replace/restore the type attribute of script elements for safe DOM manipulation -function disableScript( elem ) { - elem.type = (jQuery.find.attr( elem, "type" ) !== null) + "/" + elem.type; - return elem; -} -function restoreScript( elem ) { - var match = rscriptTypeMasked.exec( elem.type ); - if ( match ) { - elem.type = match[1]; - } else { - elem.removeAttribute("type"); - } - return elem; -} - -// Mark scripts as having already been evaluated -function setGlobalEval( elems, refElements ) { - var elem, - i = 0; - for ( ; (elem = elems[i]) != null; i++ ) { - jQuery._data( elem, "globalEval", !refElements || jQuery._data( refElements[i], "globalEval" ) ); - } -} - -function cloneCopyEvent( src, dest ) { - - if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) { - return; - } - - var type, i, l, - oldData = jQuery._data( src ), - curData = jQuery._data( dest, oldData ), - events = oldData.events; - - if ( events ) { - delete curData.handle; - curData.events = {}; - - for ( type in events ) { - for ( i = 0, l = events[ type ].length; i < l; i++ ) { - jQuery.event.add( dest, type, events[ type ][ i ] ); - } - } - } - - // make the cloned public data object a copy from the original - if ( curData.data ) { - curData.data = jQuery.extend( {}, curData.data ); - } -} - -function fixCloneNodeIssues( src, dest ) { - var nodeName, e, data; - - // We do not need to do anything for non-Elements - if ( dest.nodeType !== 1 ) { - return; - } - - nodeName = dest.nodeName.toLowerCase(); - - // IE6-8 copies events bound via attachEvent when using cloneNode. - if ( !support.noCloneEvent && dest[ jQuery.expando ] ) { - data = jQuery._data( dest ); - - for ( e in data.events ) { - jQuery.removeEvent( dest, e, data.handle ); - } - - // Event data gets referenced instead of copied if the expando gets copied too - dest.removeAttribute( jQuery.expando ); - } - - // IE blanks contents when cloning scripts, and tries to evaluate newly-set text - if ( nodeName === "script" && dest.text !== src.text ) { - disableScript( dest ).text = src.text; - restoreScript( dest ); - - // IE6-10 improperly clones children of object elements using classid. - // IE10 throws NoModificationAllowedError if parent is null, #12132. - } else if ( nodeName === "object" ) { - if ( dest.parentNode ) { - dest.outerHTML = src.outerHTML; - } - - // This path appears unavoidable for IE9. When cloning an object - // element in IE9, the outerHTML strategy above is not sufficient. - // If the src has innerHTML and the destination does not, - // copy the src.innerHTML into the dest.innerHTML. #10324 - if ( support.html5Clone && ( src.innerHTML && !jQuery.trim(dest.innerHTML) ) ) { - dest.innerHTML = src.innerHTML; - } - - } else if ( nodeName === "input" && rcheckableType.test( src.type ) ) { - // IE6-8 fails to persist the checked state of a cloned checkbox - // or radio button. Worse, IE6-7 fail to give the cloned element - // a checked appearance if the defaultChecked value isn't also set - - dest.defaultChecked = dest.checked = src.checked; - - // IE6-7 get confused and end up setting the value of a cloned - // checkbox/radio button to an empty string instead of "on" - if ( dest.value !== src.value ) { - dest.value = src.value; - } - - // IE6-8 fails to return the selected option to the default selected - // state when cloning options - } else if ( nodeName === "option" ) { - dest.defaultSelected = dest.selected = src.defaultSelected; - - // IE6-8 fails to set the defaultValue to the correct value when - // cloning other types of input fields - } else if ( nodeName === "input" || nodeName === "textarea" ) { - dest.defaultValue = src.defaultValue; - } -} - -jQuery.extend({ - clone: function( elem, dataAndEvents, deepDataAndEvents ) { - var destElements, node, clone, i, srcElements, - inPage = jQuery.contains( elem.ownerDocument, elem ); - - if ( support.html5Clone || jQuery.isXMLDoc(elem) || !rnoshimcache.test( "<" + elem.nodeName + ">" ) ) { - clone = elem.cloneNode( true ); - - // IE<=8 does not properly clone detached, unknown element nodes - } else { - fragmentDiv.innerHTML = elem.outerHTML; - fragmentDiv.removeChild( clone = fragmentDiv.firstChild ); - } - - if ( (!support.noCloneEvent || !support.noCloneChecked) && - (elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) { - - // We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2 - destElements = getAll( clone ); - srcElements = getAll( elem ); - - // Fix all IE cloning issues - for ( i = 0; (node = srcElements[i]) != null; ++i ) { - // Ensure that the destination node is not null; Fixes #9587 - if ( destElements[i] ) { - fixCloneNodeIssues( node, destElements[i] ); - } - } - } - - // Copy the events from the original to the clone - if ( dataAndEvents ) { - if ( deepDataAndEvents ) { - srcElements = srcElements || getAll( elem ); - destElements = destElements || getAll( clone ); - - for ( i = 0; (node = srcElements[i]) != null; i++ ) { - cloneCopyEvent( node, destElements[i] ); - } - } else { - cloneCopyEvent( elem, clone ); - } - } - - // Preserve script evaluation history - destElements = getAll( clone, "script" ); - if ( destElements.length > 0 ) { - setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); - } - - destElements = srcElements = node = null; - - // Return the cloned set - return clone; - }, - - buildFragment: function( elems, context, scripts, selection ) { - var j, elem, contains, - tmp, tag, tbody, wrap, - l = elems.length, - - // Ensure a safe fragment - safe = createSafeFragment( context ), - - nodes = [], - i = 0; - - for ( ; i < l; i++ ) { - elem = elems[ i ]; - - if ( elem || elem === 0 ) { - - // Add nodes directly - if ( jQuery.type( elem ) === "object" ) { - jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); - - // Convert non-html into a text node - } else if ( !rhtml.test( elem ) ) { - nodes.push( context.createTextNode( elem ) ); - - // Convert html into DOM nodes - } else { - tmp = tmp || safe.appendChild( context.createElement("div") ); - - // Deserialize a standard representation - tag = (rtagName.exec( elem ) || [ "", "" ])[ 1 ].toLowerCase(); - wrap = wrapMap[ tag ] || wrapMap._default; - - tmp.innerHTML = wrap[1] + elem.replace( rxhtmlTag, "<$1>" ) + wrap[2]; - - // Descend through wrappers to the right content - j = wrap[0]; - while ( j-- ) { - tmp = tmp.lastChild; - } - - // Manually add leading whitespace removed by IE - if ( !support.leadingWhitespace && rleadingWhitespace.test( elem ) ) { - nodes.push( context.createTextNode( rleadingWhitespace.exec( elem )[0] ) ); - } - - // Remove IE's autoinserted from table fragments - if ( !support.tbody ) { - - // String was a , *may* have spurious - elem = tag === "table" && !rtbody.test( elem ) ? - tmp.firstChild : - - // String was a bare or - wrap[1] === "
" && !rtbody.test( elem ) ? - tmp : - 0; - - j = elem && elem.childNodes.length; - while ( j-- ) { - if ( jQuery.nodeName( (tbody = elem.childNodes[j]), "tbody" ) && !tbody.childNodes.length ) { - elem.removeChild( tbody ); - } - } - } - - jQuery.merge( nodes, tmp.childNodes ); - - // Fix #12392 for WebKit and IE > 9 - tmp.textContent = ""; - - // Fix #12392 for oldIE - while ( tmp.firstChild ) { - tmp.removeChild( tmp.firstChild ); - } - - // Remember the top-level container for proper cleanup - tmp = safe.lastChild; - } - } - } - - // Fix #11356: Clear elements from fragment - if ( tmp ) { - safe.removeChild( tmp ); - } - - // Reset defaultChecked for any radios and checkboxes - // about to be appended to the DOM in IE 6/7 (#8060) - if ( !support.appendChecked ) { - jQuery.grep( getAll( nodes, "input" ), fixDefaultChecked ); - } - - i = 0; - while ( (elem = nodes[ i++ ]) ) { - - // #4087 - If origin and destination elements are the same, and this is - // that element, do not do anything - if ( selection && jQuery.inArray( elem, selection ) !== -1 ) { - continue; - } - - contains = jQuery.contains( elem.ownerDocument, elem ); - - // Append to fragment - tmp = getAll( safe.appendChild( elem ), "script" ); - - // Preserve script evaluation history - if ( contains ) { - setGlobalEval( tmp ); - } - - // Capture executables - if ( scripts ) { - j = 0; - while ( (elem = tmp[ j++ ]) ) { - if ( rscriptType.test( elem.type || "" ) ) { - scripts.push( elem ); - } - } - } - } - - tmp = null; - - return safe; - }, - - cleanData: function( elems, /* internal */ acceptData ) { - var elem, type, id, data, - i = 0, - internalKey = jQuery.expando, - cache = jQuery.cache, - deleteExpando = support.deleteExpando, - special = jQuery.event.special; - - for ( ; (elem = elems[i]) != null; i++ ) { - if ( acceptData || jQuery.acceptData( elem ) ) { - - id = elem[ internalKey ]; - data = id && cache[ id ]; - - if ( data ) { - if ( data.events ) { - for ( type in data.events ) { - if ( special[ type ] ) { - jQuery.event.remove( elem, type ); - - // This is a shortcut to avoid jQuery.event.remove's overhead - } else { - jQuery.removeEvent( elem, type, data.handle ); - } - } - } - - // Remove cache only if it was not already removed by jQuery.event.remove - if ( cache[ id ] ) { - - delete cache[ id ]; - - // IE does not allow us to delete expando properties from nodes, - // nor does it have a removeAttribute function on Document nodes; - // we must handle all of these cases - if ( deleteExpando ) { - delete elem[ internalKey ]; - - } else if ( typeof elem.removeAttribute !== strundefined ) { - elem.removeAttribute( internalKey ); - - } else { - elem[ internalKey ] = null; - } - - deletedIds.push( id ); - } - } - } - } - } -}); - -jQuery.fn.extend({ - text: function( value ) { - return access( this, function( value ) { - return value === undefined ? - jQuery.text( this ) : - this.empty().append( ( this[0] && this[0].ownerDocument || document ).createTextNode( value ) ); - }, null, value, arguments.length ); - }, - - append: function() { - return this.domManip( arguments, function( elem ) { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - var target = manipulationTarget( this, elem ); - target.appendChild( elem ); - } - }); - }, - - prepend: function() { - return this.domManip( arguments, function( elem ) { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - var target = manipulationTarget( this, elem ); - target.insertBefore( elem, target.firstChild ); - } - }); - }, - - before: function() { - return this.domManip( arguments, function( elem ) { - if ( this.parentNode ) { - this.parentNode.insertBefore( elem, this ); - } - }); - }, - - after: function() { - return this.domManip( arguments, function( elem ) { - if ( this.parentNode ) { - this.parentNode.insertBefore( elem, this.nextSibling ); - } - }); - }, - - remove: function( selector, keepData /* Internal Use Only */ ) { - var elem, - elems = selector ? jQuery.filter( selector, this ) : this, - i = 0; - - for ( ; (elem = elems[i]) != null; i++ ) { - - if ( !keepData && elem.nodeType === 1 ) { - jQuery.cleanData( getAll( elem ) ); - } - - if ( elem.parentNode ) { - if ( keepData && jQuery.contains( elem.ownerDocument, elem ) ) { - setGlobalEval( getAll( elem, "script" ) ); - } - elem.parentNode.removeChild( elem ); - } - } - - return this; - }, - - empty: function() { - var elem, - i = 0; - - for ( ; (elem = this[i]) != null; i++ ) { - // Remove element nodes and prevent memory leaks - if ( elem.nodeType === 1 ) { - jQuery.cleanData( getAll( elem, false ) ); - } - - // Remove any remaining nodes - while ( elem.firstChild ) { - elem.removeChild( elem.firstChild ); - } - - // If this is a select, ensure that it displays empty (#12336) - // Support: IE<9 - if ( elem.options && jQuery.nodeName( elem, "select" ) ) { - elem.options.length = 0; - } - } - - return this; - }, - - clone: function( dataAndEvents, deepDataAndEvents ) { - dataAndEvents = dataAndEvents == null ? false : dataAndEvents; - deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; - - return this.map(function() { - return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); - }); - }, - - html: function( value ) { - return access( this, function( value ) { - var elem = this[ 0 ] || {}, - i = 0, - l = this.length; - - if ( value === undefined ) { - return elem.nodeType === 1 ? - elem.innerHTML.replace( rinlinejQuery, "" ) : - undefined; - } - - // See if we can take a shortcut and just use innerHTML - if ( typeof value === "string" && !rnoInnerhtml.test( value ) && - ( support.htmlSerialize || !rnoshimcache.test( value ) ) && - ( support.leadingWhitespace || !rleadingWhitespace.test( value ) ) && - !wrapMap[ (rtagName.exec( value ) || [ "", "" ])[ 1 ].toLowerCase() ] ) { - - value = value.replace( rxhtmlTag, "<$1>" ); - - try { - for (; i < l; i++ ) { - // Remove element nodes and prevent memory leaks - elem = this[i] || {}; - if ( elem.nodeType === 1 ) { - jQuery.cleanData( getAll( elem, false ) ); - elem.innerHTML = value; - } - } - - elem = 0; - - // If using innerHTML throws an exception, use the fallback method - } catch(e) {} - } - - if ( elem ) { - this.empty().append( value ); - } - }, null, value, arguments.length ); - }, - - replaceWith: function() { - var arg = arguments[ 0 ]; - - // Make the changes, replacing each context element with the new content - this.domManip( arguments, function( elem ) { - arg = this.parentNode; - - jQuery.cleanData( getAll( this ) ); - - if ( arg ) { - arg.replaceChild( elem, this ); - } - }); - - // Force removal if there was no new content (e.g., from empty arguments) - return arg && (arg.length || arg.nodeType) ? this : this.remove(); - }, - - detach: function( selector ) { - return this.remove( selector, true ); - }, - - domManip: function( args, callback ) { - - // Flatten any nested arrays - args = concat.apply( [], args ); - - var first, node, hasScripts, - scripts, doc, fragment, - i = 0, - l = this.length, - set = this, - iNoClone = l - 1, - value = args[0], - isFunction = jQuery.isFunction( value ); - - // We can't cloneNode fragments that contain checked, in WebKit - if ( isFunction || - ( l > 1 && typeof value === "string" && - !support.checkClone && rchecked.test( value ) ) ) { - return this.each(function( index ) { - var self = set.eq( index ); - if ( isFunction ) { - args[0] = value.call( this, index, self.html() ); - } - self.domManip( args, callback ); - }); - } - - if ( l ) { - fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, this ); - first = fragment.firstChild; - - if ( fragment.childNodes.length === 1 ) { - fragment = first; - } - - if ( first ) { - scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); - hasScripts = scripts.length; - - // Use the original fragment for the last item instead of the first because it can end up - // being emptied incorrectly in certain situations (#8070). - for ( ; i < l; i++ ) { - node = fragment; - - if ( i !== iNoClone ) { - node = jQuery.clone( node, true, true ); - - // Keep references to cloned scripts for later restoration - if ( hasScripts ) { - jQuery.merge( scripts, getAll( node, "script" ) ); - } - } - - callback.call( this[i], node, i ); - } - - if ( hasScripts ) { - doc = scripts[ scripts.length - 1 ].ownerDocument; - - // Reenable scripts - jQuery.map( scripts, restoreScript ); - - // Evaluate executable scripts on first document insertion - for ( i = 0; i < hasScripts; i++ ) { - node = scripts[ i ]; - if ( rscriptType.test( node.type || "" ) && - !jQuery._data( node, "globalEval" ) && jQuery.contains( doc, node ) ) { - - if ( node.src ) { - // Optional AJAX dependency, but won't run scripts if not present - if ( jQuery._evalUrl ) { - jQuery._evalUrl( node.src ); - } - } else { - jQuery.globalEval( ( node.text || node.textContent || node.innerHTML || "" ).replace( rcleanScript, "" ) ); - } - } - } - } - - // Fix #11809: Avoid leaking memory - fragment = first = null; - } - } - - return this; - } -}); - -jQuery.each({ - appendTo: "append", - prependTo: "prepend", - insertBefore: "before", - insertAfter: "after", - replaceAll: "replaceWith" -}, function( name, original ) { - jQuery.fn[ name ] = function( selector ) { - var elems, - i = 0, - ret = [], - insert = jQuery( selector ), - last = insert.length - 1; - - for ( ; i <= last; i++ ) { - elems = i === last ? this : this.clone(true); - jQuery( insert[i] )[ original ]( elems ); - - // Modern browsers can apply jQuery collections as arrays, but oldIE needs a .get() - push.apply( ret, elems.get() ); - } - - return this.pushStack( ret ); - }; -}); - - -var iframe, - elemdisplay = {}; - -/** - * Retrieve the actual display of a element - * @param {String} name nodeName of the element - * @param {Object} doc Document object - */ -// Called only from within defaultDisplay -function actualDisplay( name, doc ) { - var style, - elem = jQuery( doc.createElement( name ) ).appendTo( doc.body ), - - // getDefaultComputedStyle might be reliably used only on attached element - display = window.getDefaultComputedStyle && ( style = window.getDefaultComputedStyle( elem[ 0 ] ) ) ? - - // Use of this method is a temporary fix (more like optmization) until something better comes along, - // since it was removed from specification and supported only in FF - style.display : jQuery.css( elem[ 0 ], "display" ); - - // We don't have any data stored on the element, - // so use "detach" method as fast way to get rid of the element - elem.detach(); - - return display; -} - -/** - * Try to determine the default display value of an element - * @param {String} nodeName - */ -function defaultDisplay( nodeName ) { - var doc = document, - display = elemdisplay[ nodeName ]; - - if ( !display ) { - display = actualDisplay( nodeName, doc ); - - // If the simple way fails, read from inside an iframe - if ( display === "none" || !display ) { - - // Use the already-created iframe if possible - iframe = (iframe || jQuery( "