Repository: nipreps/mriqc Branch: master Commit: c030ac1919de Files: 173 Total size: 17.6 MB Directory structure: gitextract_jfsbcv57/ ├── .circleci/ │ ├── circle_T1w.txt │ ├── circle_bold.txt │ └── config.yml ├── .codecov.yml ├── .dockerignore ├── .git_archival.txt ├── .gitattributes ├── .github/ │ ├── config.yml │ ├── dependabot.yml │ └── workflows/ │ └── pythonpackage.yml ├── .gitignore ├── .mailmap ├── .maint/ │ ├── CONTRIBUTORS.md │ ├── FORMER.md │ ├── MAINTAINERS.md │ ├── PIs.md │ ├── ROADMAP.md │ ├── requirements.txt │ ├── update_authors.py │ └── update_changes.sh ├── .readthedocs.yaml ├── .zenodo.json ├── AGENTS.md ├── CHANGES.rst ├── Dockerfile ├── Dockerfile_devel ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.rst ├── docker/ │ └── files/ │ └── neurodebian.gpg ├── docs/ │ ├── Makefile │ ├── notebooks/ │ │ ├── .gitignore │ │ ├── MRIQC Web API.ipynb │ │ ├── Paper-v1.0.ipynb │ │ ├── Paper-v2.0.ipynb │ │ ├── SpikesPlotter.ipynb │ │ ├── Supplemental Materials.ipynb │ │ └── finding_spikes.ipynb │ └── source/ │ ├── _static/ │ │ ├── bold-1subject-1task.html │ │ ├── bold-1subject-8tasks.html │ │ ├── example_anatreport.html │ │ └── example_funcreport.html │ ├── about.rst │ ├── changes.rst │ ├── conf.py │ ├── dsa.rst │ ├── index.rst │ ├── install.rst │ ├── iqms/ │ │ ├── bold.rst │ │ ├── dwi.rst │ │ └── t1w.rst │ ├── license.rst │ ├── measures.rst │ ├── reports/ │ │ ├── bold.rst │ │ ├── group.rst │ │ └── smri.rst │ ├── reports.rst │ ├── resources/ │ │ ├── mriqc.sbatch │ │ └── sbatch.sh │ ├── usage.rst │ └── workflows.rst ├── long_description.rst ├── mriqc/ │ ├── __init__.py │ ├── __main__.py │ ├── _warnings.py │ ├── bin/ │ │ ├── __init__.py │ │ ├── abide2bids.py │ │ ├── dfcheck.py │ │ ├── fs2gif.py │ │ ├── labeler.py │ │ ├── messages.py │ │ ├── mriqcwebapi_test.py │ │ ├── nib_hash.py │ │ └── subject_wrangler.py │ ├── cli/ │ │ ├── __init__.py │ │ ├── parser.py │ │ ├── run.py │ │ ├── version.py │ │ └── workflow.py │ ├── config.py │ ├── conftest.py │ ├── data/ │ │ ├── NOTICE │ │ ├── __init__.py │ │ ├── bootstrap-anat.yml │ │ ├── bootstrap-dwi.yml │ │ ├── bootstrap-func.yml │ │ ├── config-example.toml │ │ ├── config.py │ │ ├── fsexport.tcl │ │ ├── itk_identity.tfm │ │ ├── reports/ │ │ │ ├── embed_resources/ │ │ │ │ ├── boxplots.css │ │ │ │ └── boxplots.js │ │ │ ├── group.html │ │ │ └── resources/ │ │ │ ├── DO_NOT_REMOVE_OR_MODIFY │ │ │ ├── boxplots.css │ │ │ └── boxplots.js │ │ ├── testdata/ │ │ │ ├── group_T1w.tsv │ │ │ └── group_bold.tsv │ │ └── tests/ │ │ ├── ds000005/ │ │ │ ├── CHANGES │ │ │ ├── README │ │ │ ├── dataset_description.json │ │ │ ├── participants.tsv │ │ │ ├── sub-01/ │ │ │ │ └── func/ │ │ │ │ ├── sub-01_task-mixedgamblestask_run-01_bold.json │ │ │ │ ├── sub-01_task-mixedgamblestask_run-01_events.tsv │ │ │ │ ├── sub-01_task-mixedgamblestask_run-02_events.tsv │ │ │ │ └── sub-01_task-mixedgamblestask_run-03_events.tsv │ │ │ └── task-mixedgamblestask_bold.json │ │ ├── ds002785/ │ │ │ ├── dataset_description.json │ │ │ ├── sub-0017/ │ │ │ │ └── anat/ │ │ │ │ └── sub-0017_T1w.json │ │ │ └── sub-0042/ │ │ │ └── anat/ │ │ │ └── sub-0042_T1w.json │ │ ├── gh1086-ds004134.oracle │ │ ├── gh921-dmd-20220428-0.oracle │ │ └── gh921-dmd-20230319-0.oracle │ ├── engine/ │ │ ├── __init__.py │ │ └── plugin.py │ ├── instrumentation/ │ │ ├── __init__.py │ │ ├── __main__.py │ │ ├── resources.py │ │ └── viz.py │ ├── interfaces/ │ │ ├── __init__.py │ │ ├── anatomical.py │ │ ├── bids.py │ │ ├── common/ │ │ │ ├── __init__.py │ │ │ ├── conform_image.py │ │ │ └── ensure_size.py │ │ ├── diffusion.py │ │ ├── functional.py │ │ ├── reports.py │ │ ├── synthstrip.py │ │ ├── tests/ │ │ │ └── test_interfaces.py │ │ ├── transitional.py │ │ └── webapi.py │ ├── messages.py │ ├── qc/ │ │ ├── __init__.py │ │ ├── anatomical.py │ │ ├── diffusion.py │ │ ├── functional.py │ │ └── tests/ │ │ ├── __init__.py │ │ ├── test_anatomical.py │ │ └── test_diffusion.py │ ├── reports/ │ │ ├── __init__.py │ │ ├── group.py │ │ └── individual.py │ ├── synthstrip/ │ │ ├── ORIGINAL_LICENSE │ │ ├── __init__.py │ │ ├── __main__.py │ │ ├── cli.py │ │ └── model.py │ ├── testing.py │ ├── tests/ │ │ ├── test_config.py │ │ ├── test_main.py │ │ ├── test_parser.py │ │ └── test_reports.py │ ├── utils/ │ │ ├── __init__.py │ │ ├── bids.py │ │ ├── debug.py │ │ ├── misc.py │ │ └── telemetry.py │ └── workflows/ │ ├── __init__.py │ ├── anatomical/ │ │ ├── __init__.py │ │ ├── base.py │ │ └── output.py │ ├── core.py │ ├── diffusion/ │ │ ├── __init__.py │ │ ├── base.py │ │ └── output.py │ ├── functional/ │ │ ├── __init__.py │ │ ├── base.py │ │ └── output.py │ ├── shared.py │ └── utils.py └── pyproject.toml ================================================ FILE CONTENTS ================================================ ================================================ FILE: .circleci/circle_T1w.txt ================================================ .bids_db .bids_db/layout_index.sqlite .bidsignore dataset_description.json group_T1w.html group_T1w.tsv logs logs/config-18480913-163000_PhineasG-ageh-adhi-sacc-ident9b1ab0f.toml logs/mriqc-18480913-163000_PhineasG-ageh-adhi-sacc-ident9b1ab0f.log sub-50137 sub-50137/anat sub-50137/anat/sub-50137_T1w.json sub-50137/figures sub-50137/figures/sub-50137_desc-airmask_T1w.svg sub-50137/figures/sub-50137_desc-artifacts_T1w.svg sub-50137/figures/sub-50137_desc-background_T1w.svg sub-50137/figures/sub-50137_desc-brainmask_T1w.svg sub-50137/figures/sub-50137_desc-head_T1w.svg sub-50137/figures/sub-50137_desc-noisefit_T1w.svg sub-50137/figures/sub-50137_desc-norm_T1w.svg sub-50137/figures/sub-50137_desc-segmentation_T1w.svg sub-50137/figures/sub-50137_desc-zoomed_T1w.svg sub-50137_T1w.html sub-50152 sub-50152/anat sub-50152/anat/sub-50152_T1w.json sub-50152/figures sub-50152/figures/sub-50152_desc-airmask_T1w.svg sub-50152/figures/sub-50152_desc-artifacts_T1w.svg sub-50152/figures/sub-50152_desc-background_T1w.svg sub-50152/figures/sub-50152_desc-brainmask_T1w.svg sub-50152/figures/sub-50152_desc-head_T1w.svg sub-50152/figures/sub-50152_desc-noisefit_T1w.svg sub-50152/figures/sub-50152_desc-norm_T1w.svg sub-50152/figures/sub-50152_desc-segmentation_T1w.svg sub-50152/figures/sub-50152_desc-zoomed_T1w.svg sub-50152_T1w.html sub-50785 sub-50785/anat sub-50785/anat/sub-50785_T1w.json sub-50785/figures sub-50785/figures/sub-50785_desc-airmask_T1w.svg sub-50785/figures/sub-50785_desc-artifacts_T1w.svg sub-50785/figures/sub-50785_desc-background_T1w.svg sub-50785/figures/sub-50785_desc-brainmask_T1w.svg sub-50785/figures/sub-50785_desc-head_T1w.svg sub-50785/figures/sub-50785_desc-noisefit_T1w.svg sub-50785/figures/sub-50785_desc-norm_T1w.svg sub-50785/figures/sub-50785_desc-segmentation_T1w.svg sub-50785/figures/sub-50785_desc-zoomed_T1w.svg sub-50785_T1w.html sub-51187 sub-51187/anat sub-51187/anat/sub-51187_T1w.json sub-51187/figures sub-51187/figures/sub-51187_desc-airmask_T1w.svg sub-51187/figures/sub-51187_desc-artifacts_T1w.svg sub-51187/figures/sub-51187_desc-background_T1w.svg sub-51187/figures/sub-51187_desc-brainmask_T1w.svg sub-51187/figures/sub-51187_desc-head_T1w.svg sub-51187/figures/sub-51187_desc-noisefit_T1w.svg sub-51187/figures/sub-51187_desc-norm_T1w.svg sub-51187/figures/sub-51187_desc-segmentation_T1w.svg sub-51187/figures/sub-51187_desc-zoomed_T1w.svg sub-51187_T1w.html /tmp/t1w/derivatives ================================================ FILE: .circleci/circle_bold.txt ================================================ .bids_db .bids_db/layout_index.sqlite .bidsignore dataset_description.json group_bold.html group_bold.tsv logs logs/config-18480913-163000_PhineasG-ageh-adhi-sacc-ident9b1ab0f.toml logs/mriqc-18480913-163000_PhineasG-ageh-adhi-sacc-ident9b1ab0f.log sub-ds205s03 sub-ds205s03/figures sub-ds205s03/figures/sub-ds205s03_task-functionallocalizer_run-01_desc-background_bold.svg sub-ds205s03/figures/sub-ds205s03_task-functionallocalizer_run-01_desc-brainmask_bold.svg sub-ds205s03/figures/sub-ds205s03_task-functionallocalizer_run-01_desc-carpet_bold.svg sub-ds205s03/figures/sub-ds205s03_task-functionallocalizer_run-01_desc-mean_bold.svg sub-ds205s03/figures/sub-ds205s03_task-functionallocalizer_run-01_desc-norm_bold.svg sub-ds205s03/figures/sub-ds205s03_task-functionallocalizer_run-01_desc-stdev_bold.svg sub-ds205s03/figures/sub-ds205s03_task-functionallocalizer_run-01_desc-zoomed_bold.svg sub-ds205s03/figures/sub-ds205s03_task-view_run-01_desc-background_bold.svg sub-ds205s03/figures/sub-ds205s03_task-view_run-01_desc-brainmask_bold.svg sub-ds205s03/figures/sub-ds205s03_task-view_run-01_desc-carpet_bold.svg sub-ds205s03/figures/sub-ds205s03_task-view_run-01_desc-mean_bold.svg sub-ds205s03/figures/sub-ds205s03_task-view_run-01_desc-norm_bold.svg sub-ds205s03/figures/sub-ds205s03_task-view_run-01_desc-stdev_bold.svg sub-ds205s03/figures/sub-ds205s03_task-view_run-01_desc-zoomed_bold.svg sub-ds205s03/figures/sub-ds205s03_task-view_run-02_desc-background_bold.svg sub-ds205s03/figures/sub-ds205s03_task-view_run-02_desc-brainmask_bold.svg sub-ds205s03/figures/sub-ds205s03_task-view_run-02_desc-carpet_bold.svg sub-ds205s03/figures/sub-ds205s03_task-view_run-02_desc-mean_bold.svg sub-ds205s03/figures/sub-ds205s03_task-view_run-02_desc-norm_bold.svg sub-ds205s03/figures/sub-ds205s03_task-view_run-02_desc-stdev_bold.svg sub-ds205s03/figures/sub-ds205s03_task-view_run-02_desc-zoomed_bold.svg sub-ds205s03/func sub-ds205s03/func/sub-ds205s03_task-functionallocalizer_run-01_bold.json sub-ds205s03/func/sub-ds205s03_task-functionallocalizer_run-01_timeseries.json sub-ds205s03/func/sub-ds205s03_task-functionallocalizer_run-01_timeseries.tsv sub-ds205s03/func/sub-ds205s03_task-view_run-01_bold.json sub-ds205s03/func/sub-ds205s03_task-view_run-01_timeseries.json sub-ds205s03/func/sub-ds205s03_task-view_run-01_timeseries.tsv sub-ds205s03/func/sub-ds205s03_task-view_run-02_bold.json sub-ds205s03/func/sub-ds205s03_task-view_run-02_timeseries.json sub-ds205s03/func/sub-ds205s03_task-view_run-02_timeseries.tsv sub-ds205s03_task-functionallocalizer_run-01_bold.html sub-ds205s03_task-view_run-01_bold.html sub-ds205s03_task-view_run-02_bold.html sub-ds205s07 sub-ds205s07/figures sub-ds205s07/figures/sub-ds205s07_task-functionallocalizer_run-01_desc-background_bold.svg sub-ds205s07/figures/sub-ds205s07_task-functionallocalizer_run-01_desc-brainmask_bold.svg sub-ds205s07/figures/sub-ds205s07_task-functionallocalizer_run-01_desc-carpet_bold.svg sub-ds205s07/figures/sub-ds205s07_task-functionallocalizer_run-01_desc-mean_bold.svg sub-ds205s07/figures/sub-ds205s07_task-functionallocalizer_run-01_desc-norm_bold.svg sub-ds205s07/figures/sub-ds205s07_task-functionallocalizer_run-01_desc-stdev_bold.svg sub-ds205s07/figures/sub-ds205s07_task-functionallocalizer_run-01_desc-zoomed_bold.svg sub-ds205s07/figures/sub-ds205s07_task-view_run-01_desc-background_bold.svg sub-ds205s07/figures/sub-ds205s07_task-view_run-01_desc-brainmask_bold.svg sub-ds205s07/figures/sub-ds205s07_task-view_run-01_desc-carpet_bold.svg sub-ds205s07/figures/sub-ds205s07_task-view_run-01_desc-mean_bold.svg sub-ds205s07/figures/sub-ds205s07_task-view_run-01_desc-norm_bold.svg sub-ds205s07/figures/sub-ds205s07_task-view_run-01_desc-stdev_bold.svg sub-ds205s07/figures/sub-ds205s07_task-view_run-01_desc-zoomed_bold.svg sub-ds205s07/figures/sub-ds205s07_task-view_run-02_desc-background_bold.svg sub-ds205s07/figures/sub-ds205s07_task-view_run-02_desc-brainmask_bold.svg sub-ds205s07/figures/sub-ds205s07_task-view_run-02_desc-carpet_bold.svg sub-ds205s07/figures/sub-ds205s07_task-view_run-02_desc-mean_bold.svg sub-ds205s07/figures/sub-ds205s07_task-view_run-02_desc-norm_bold.svg sub-ds205s07/figures/sub-ds205s07_task-view_run-02_desc-stdev_bold.svg sub-ds205s07/figures/sub-ds205s07_task-view_run-02_desc-zoomed_bold.svg sub-ds205s07/func sub-ds205s07/func/sub-ds205s07_task-functionallocalizer_run-01_bold.json sub-ds205s07/func/sub-ds205s07_task-functionallocalizer_run-01_timeseries.json sub-ds205s07/func/sub-ds205s07_task-functionallocalizer_run-01_timeseries.tsv sub-ds205s07/func/sub-ds205s07_task-view_run-01_bold.json sub-ds205s07/func/sub-ds205s07_task-view_run-01_timeseries.json sub-ds205s07/func/sub-ds205s07_task-view_run-01_timeseries.tsv sub-ds205s07/func/sub-ds205s07_task-view_run-02_bold.json sub-ds205s07/func/sub-ds205s07_task-view_run-02_timeseries.json sub-ds205s07/func/sub-ds205s07_task-view_run-02_timeseries.tsv sub-ds205s07_task-functionallocalizer_run-01_bold.html sub-ds205s07_task-view_run-01_bold.html sub-ds205s07_task-view_run-02_bold.html sub-ds205s09 sub-ds205s09/figures sub-ds205s09/figures/sub-ds205s09_task-view_acq-LR_run-01_desc-background_bold.svg sub-ds205s09/figures/sub-ds205s09_task-view_acq-LR_run-01_desc-brainmask_bold.svg sub-ds205s09/figures/sub-ds205s09_task-view_acq-LR_run-01_desc-carpet_bold.svg sub-ds205s09/figures/sub-ds205s09_task-view_acq-LR_run-01_desc-mean_bold.svg sub-ds205s09/figures/sub-ds205s09_task-view_acq-LR_run-01_desc-norm_bold.svg sub-ds205s09/figures/sub-ds205s09_task-view_acq-LR_run-01_desc-stdev_bold.svg sub-ds205s09/figures/sub-ds205s09_task-view_acq-LR_run-01_desc-zoomed_bold.svg sub-ds205s09/figures/sub-ds205s09_task-view_acq-LR_run-02_desc-background_bold.svg sub-ds205s09/figures/sub-ds205s09_task-view_acq-LR_run-02_desc-brainmask_bold.svg sub-ds205s09/figures/sub-ds205s09_task-view_acq-LR_run-02_desc-carpet_bold.svg sub-ds205s09/figures/sub-ds205s09_task-view_acq-LR_run-02_desc-mean_bold.svg sub-ds205s09/figures/sub-ds205s09_task-view_acq-LR_run-02_desc-norm_bold.svg sub-ds205s09/figures/sub-ds205s09_task-view_acq-LR_run-02_desc-stdev_bold.svg sub-ds205s09/figures/sub-ds205s09_task-view_acq-LR_run-02_desc-zoomed_bold.svg sub-ds205s09/figures/sub-ds205s09_task-view_acq-RL_run-01_desc-background_bold.svg sub-ds205s09/figures/sub-ds205s09_task-view_acq-RL_run-01_desc-brainmask_bold.svg sub-ds205s09/figures/sub-ds205s09_task-view_acq-RL_run-01_desc-carpet_bold.svg sub-ds205s09/figures/sub-ds205s09_task-view_acq-RL_run-01_desc-mean_bold.svg sub-ds205s09/figures/sub-ds205s09_task-view_acq-RL_run-01_desc-norm_bold.svg sub-ds205s09/figures/sub-ds205s09_task-view_acq-RL_run-01_desc-stdev_bold.svg sub-ds205s09/figures/sub-ds205s09_task-view_acq-RL_run-01_desc-zoomed_bold.svg sub-ds205s09/func sub-ds205s09/func/sub-ds205s09_task-view_acq-LR_run-01_bold.json sub-ds205s09/func/sub-ds205s09_task-view_acq-LR_run-01_timeseries.json sub-ds205s09/func/sub-ds205s09_task-view_acq-LR_run-01_timeseries.tsv sub-ds205s09/func/sub-ds205s09_task-view_acq-LR_run-02_bold.json sub-ds205s09/func/sub-ds205s09_task-view_acq-LR_run-02_timeseries.json sub-ds205s09/func/sub-ds205s09_task-view_acq-LR_run-02_timeseries.tsv sub-ds205s09/func/sub-ds205s09_task-view_acq-RL_run-01_bold.json sub-ds205s09/func/sub-ds205s09_task-view_acq-RL_run-01_timeseries.json sub-ds205s09/func/sub-ds205s09_task-view_acq-RL_run-01_timeseries.tsv sub-ds205s09_task-view_acq-LR_run-01_bold.html sub-ds205s09_task-view_acq-LR_run-02_bold.html sub-ds205s09_task-view_acq-RL_run-01_bold.html /tmp/bold/derivatives ================================================ FILE: .circleci/config.yml ================================================ version: 2.1 orbs: docker: circleci/docker@2.1.4 jobs: build: environment: - TZ: "/usr/share/zoneinfo/America/Los_Angeles" - MRIQC_API_TAG: 1.1.1 - MRIQC_API_DOCKER_IMAGES: "nginx:1.26.2 swaggerapi/swagger-ui:v5.17.14 mongo:6.0.15 python:3.7-slim-bullseye" - DOCKER_BUILDKIT: 1 machine: # https://discuss.circleci.com/t/linux-machine-executor-images-2021-april-q2-update/39928 # upgrade Docker version image: default docker_layer_caching: true working_directory: /tmp/src/mriqc steps: - checkout - persist_to_workspace: root: /tmp paths: - src/mriqc - run: name: Check whether build should be skipped command: | if [[ "$( git log --format='format:%s' -n 1 $CIRCLE_SHA1 | grep -i -E '^docs?(\(\w+\))?:' )" != "" ]]; then echo "Only docs build" circleci step halt fi - restore_cache: keys: - build-v4-{{ .Branch }}-{{ .Revision }} - build-v4--{{ .Revision }} - build-v4-{{ .Branch }}- - build-v4-master- - build-v4- paths: - /tmp/docker - /tmp/images - docker/install-docker-credential-helper - run: name: Docker authentication command: | if [[ -n $DOCKER_PAT ]]; then echo "$DOCKER_PAT" | docker login -u $DOCKER_USER --password-stdin fi - run: name: Set up Docker registry command: | if [[ -f /tmp/images/registry.tar.gz ]]; then echo "Loading saved registry image" docker load < /tmp/images/registry.tar.gz else echo "Pulling registry image from DockerHub" docker pull registry:2 mkdir -p /tmp/images docker save registry:2 | gzip > /tmp/images/registry.tar.gz fi docker run -d -p 5000:5000 --restart=always --name=registry \ -v /tmp/docker:/var/lib/registry registry:2 - run: name: Pull images command: | set +e docker pull localhost:5000/miniconda success=$? set -e if [[ "$success" = "0" ]]; then echo "Pulling from local registry" docker tag localhost:5000/miniconda nipreps/miniconda:py39_2403.0 docker pull localhost:5000/mriqc docker tag localhost:5000/mriqc nipreps/mriqc:latest docker tag localhost:5000/mriqc nipreps/mriqc else echo "Pulling from Docker Hub" docker pull nipreps/miniconda:py39_2403.0 docker tag nipreps/miniconda:py39_2403.0 localhost:5000/miniconda docker push localhost:5000/miniconda docker pull nipreps/mriqc:latest fi - run: name: MRIQCWebAPI - Pull Docker images command: | webapi_images=($MRIQC_API_DOCKER_IMAGES) for image in ${webapi_images[@]}; do set +e docker pull localhost:5000/${image} success=$? set -e if [[ "$success" = "0" ]]; then docker tag localhost:5000/${image} ${image} else docker pull ${image} docker tag ${image} localhost:5000/${image} docker push localhost:5000/${image} fi done; - run: name: Prepare MRIQCWebAPI command: | set +e docker pull localhost:5000/dockereve-master-endpoints:latest success=$? set -e if [[ "$success" = 0 ]]; then docker tag localhost:5000/dockereve-master-endpoints:latest dockereve-master-endpoints:latest fi rm -rf /tmp/src/mriqcwebapi git clone https://github.com/nipreps/mriqcwebapi.git /tmp/src/mriqcwebapi cd /tmp/src/mriqcwebapi git checkout ${MRIQC_API_TAG} if [ "${MRIQC_API_SECRET_TOKEN}" != "" ]; then sed -i -E "s//$MRIQC_API_SECRET_TOKEN/" dockereve-master/.env grep -q -i $MRIQC_API_SECRET_TOKEN dockereve-master/.env fi sed -i -E \ -e 's#image: nginx:latest#image: nginx:1.26.2#' \ -e 's#image: swaggerapi/swagger-ui:latest#image: swaggerapi/swagger-ui:v5.17.14#' \ -e 's#image: mongo:latest#image: mongo:6.0.15#' \ dockereve-master/docker-compose.yml docker-compose -f /tmp/src/mriqcwebapi/dockereve-master/docker-compose.yml pull docker-compose -f /tmp/src/mriqcwebapi/dockereve-master/docker-compose.yml build docker tag dockereve-master-endpoints:latest localhost:5000/dockereve-master-endpoints:latest docker push localhost:5000/dockereve-master-endpoints:latest - run: name: Build Docker image no_output_timeout: 60m command: | pyenv local 3 pip install hatch # Get version before making repo dirty THISVERSION=$( hatch version ) # Inject MRIQC-WebAPI secret if [ "${MRIQC_API_SECRET_TOKEN}" != "" ]; then sed -i -E "s//$MRIQC_API_SECRET_TOKEN/" mriqc/config.py grep -q -i $MRIQC_API_SECRET_TOKEN mriqc/config.py fi if [[ ${THISVERSION:0:1} == "0" ]] ; then echo "WARNING: latest git tag could not be found" echo "Please, make sure you fetch all tags from upstream with" echo "the command ``git fetch --tags --verbose`` and push" echo "them to your fork with ``git push origin --tags``" fi echo "Building version: $THISVERSION." # Build docker image e=1 && for i in {1..5}; do docker build \ --cache-from=nipreps/mriqc \ -t nipreps/mriqc:latest \ --build-arg BUILD_DATE=`date -u +"%Y-%m-%dT%H:%M:%SZ"` \ --build-arg VCS_REF=`git rev-parse --short HEAD` \ --build-arg VERSION="${CIRCLE_TAG:-$THISVERSION}" . \ && e=0 && break || sleep 15 done && [ "$e" -eq "0" ] TARGET_VERSION="${CIRCLE_TAG:-$THISVERSION}" TARGET_VERSION="MRIQC v${TARGET_VERSION%+*}" DOCKER_VERSION=$( docker run --rm nipreps/mriqc:latest --version ) DOCKER_VERSION="${DOCKER_VERSION%+*}" echo "Target version: \"${TARGET_VERSION}\"" echo "Docker version: \"${DOCKER_VERSION}\"" test "${TARGET_VERSION}" == "$DOCKER_VERSION" - run: name: Docker push to local registry no_output_timeout: 40m command: | docker tag nipreps/mriqc:latest localhost:5000/mriqc docker push localhost:5000/mriqc webapi_images=($MRIQC_API_DOCKER_IMAGES) for image in ${webapi_images[@]}; do docker tag ${image} localhost:5000/${image} docker push localhost:5000/${image} done - run: name: Docker registry garbage collection command: | docker exec -it registry /bin/registry garbage-collect --delete-untagged \ /etc/docker/registry/config.yml - save_cache: key: build-v4-{{ .Branch }}-{{ .Revision }} paths: - /tmp/docker - /tmp/images - persist_to_workspace: root: /tmp paths: - src/mriqc - src/mriqcwebapi get_data: machine: image: ubuntu-2204:2023.02.1 environment: - TZ: "/usr/share/zoneinfo/America/Los_Angeles" - TEST_DATA_NAME: "circle-tests" - TEST_DATA_URL: "https://files.osf.io/v1/resources/fvuh8/providers/osfstorage/5b\ 6c9950fed49e001a7885b6" working_directory: /home/circleci/data steps: - checkout: path: /tmp/src/mriqc - run: name: Check whether build should be skipped command: | cd /tmp/src/mriqc if [[ "$( git log --format='format:%s' -n 1 $CIRCLE_SHA1 | grep -i -E '^docs?(\(\w+\))?:' )" != "" ]]; then echo "Only docs build" circleci step halt fi - restore_cache: keys: - data-v2-{{ epoch }} - data-v2- - run: name: Get test data command: | mkdir -p /tmp/data if [[ ! -d /tmp/data/${TEST_DATA_NAME} ]]; then wget --retry-connrefused --waitretry=5 --read-timeout=20 --timeout=15 -t 0 -q \ -O ${TEST_DATA_NAME}.tar.gz "${TEST_DATA_URL}" tar xvzf ${TEST_DATA_NAME}.tar.gz -C /tmp/data/ else echo "Dataset ${TEST_DATA_NAME} was cached" fi - run: name: Create Nipype config files command: | mkdir -p /tmp/t1w /tmp/bold printf "[execution]\nstop_on_first_crash = true\n" > /tmp/t1w/nipype.cfg echo "poll_sleep_duration = 0.01" >> /tmp/t1w/nipype.cfg echo "hash_method = content" >> /tmp/t1w/nipype.cfg cp /tmp/t1w/nipype.cfg /tmp/bold/nipype.cfg - save_cache: key: data-v2-{{ epoch }} paths: - /tmp/data - /tmp/t1w - /tmp/bold test_pytest: machine: image: ubuntu-2204:2023.02.1 working_directory: /home/circleci/out/tests steps: - attach_workspace: at: /tmp - run: name: Check whether build should be skipped command: | cd /tmp/src/mriqc if [[ "$( git log --format='format:%s' -n 1 $CIRCLE_SHA1 | grep -i -E '^docs?(\(\w+\))?:' )" != "" ]]; then echo "Only docs build" circleci step halt fi if [[ "$( git log --format=oneline -n 1 $CIRCLE_SHA1 | grep -i -E '\[only[ _]?(anat|func|smoke)\]' )" != "" ]]; then echo "Only smoke-tests build" circleci step halt fi - restore_cache: keys: - build-v4-{{ .Branch }}-{{ .Revision }} paths: - /tmp/docker - /tmp/images - docker/install-docker-credential-helper - run: name: Docker authentication command: | if [[ -n $DOCKER_PAT ]]; then echo "$DOCKER_PAT" | docker login -u $DOCKER_USER --password-stdin fi - run: name: Set-up a Docker registry command: | if [[ -f /tmp/images/registry.tar.gz ]]; then echo "Loading saved registry image" docker load < /tmp/images/registry.tar.gz else echo "Pulling registry image from DockerHub" docker pull registry:2 fi docker run -d -p 5000:5000 --restart=always --name=registry \ -v /tmp/docker:/var/lib/registry registry:2 - run: name: Pull images from local registry command: | docker pull localhost:5000/mriqc docker tag localhost:5000/mriqc nipreps/mriqc:latest - run: name: Generate _version.py command: | python3 -m pip install build pushd /tmp/src/mriqc python3 -m build popd - run: name: Run MRIQC tests no_output_timeout: 2h command: | docker run --rm -ti -v $PWD:/scratch --entrypoint="pytest" \ -v /tmp/src:/src -w /src/mriqc \ nipreps/mriqc:latest mriqc \ --junitxml=/scratch/tests.xml \ --doctest-modules --ignore=mriqc/bin \ --ignore=mriqc/interfaces/transitional.py - store_test_results: path: /home/circleci/out/tests T1w: environment: - TZ: "/usr/share/zoneinfo/America/Los_Angeles" - TEST_DATA_NAME: "circle-tests" - MRIQC_API_DOCKER_IMAGES: "nginx:1.26.2 swaggerapi/swagger-ui:v5.17.14 mongo:6.0.15 python:3.7-slim-bullseye dockereve-master-endpoints:latest" - MIGAS_OPTOUT: "1" machine: # https://discuss.circleci.com/t/linux-machine-executor-images-2021-april-q2-update/39928 # upgrade Docker version image: ubuntu-2204:2023.02.1 resource_class: large working_directory: /tmp/t1w steps: - attach_workspace: at: /tmp - run: name: Check whether build should be skipped command: | cd /tmp/src/mriqc if [[ "$( git log --format='format:%s' -n 1 $CIRCLE_SHA1 | grep -i -E '^docs?(\(\w+\))?:' )" != "" ]]; then echo "Only docs build" circleci step halt fi if [[ "$( git log --format=oneline -n 1 $CIRCLE_SHA1 | grep -i -E '\[only[ _]?func\]' )" != "" ]]; then echo "Only functional smoke-tests build" circleci step halt fi - restore_cache: keys: - build-v4-{{ .Branch }}-{{ .Revision }} paths: - /tmp/docker - /tmp/images - run: name: Set-up a Docker registry command: | if [[ -f /tmp/images/registry.tar.gz ]]; then echo "Loading saved registry image" docker load < /tmp/images/registry.tar.gz else echo "Pulling registry image from DockerHub" docker pull registry:2 fi docker run -d --restart=always --name=registry \ -e REGISTRY_HTTP_ADDR=0.0.0.0:5001 -p 5001:5001 \ -v /tmp/docker:/var/lib/registry registry:2 - run: name: Pull images from local registry command: | docker pull localhost:5001/mriqc docker tag localhost:5001/mriqc nipreps/mriqc:latest webapi_images=($MRIQC_API_DOCKER_IMAGES) for image in ${webapi_images[@]}; do docker pull localhost:5001/${image} docker tag localhost:5001/${image} ${image} done; - run: name: Start MRIQC WebAPI endpoint command: | docker-compose -f /tmp/src/mriqcwebapi/dockereve-master/docker-compose.yml --verbose up -d background: true - restore_cache: keys: - data-v2-{{ epoch }} - data-v2- - restore_cache: keys: - t1w-v6-{{ .Branch }} - t1w-v6-master - t1w-v6- - run: name: Remove old, cached configs command: | rm -f /tmp/t1w/work/.mriqc.*.toml rm -f /tmp/t1w/work/.resources.*.tsv rm -f /tmp/t1w/work/.resources.*.png - run: name: Run participant-level on T1w images no_output_timeout: 2h command: | mkdir -p /tmp/t1w/work /tmp/t1w/derivatives # Run MRIQC docker run -u $( id -u ) --rm -ti \ -v /tmp/data/${TEST_DATA_NAME}:/data:ro \ -v /tmp/t1w:/scratch -w /scratch \ -e MRIQC_DEV=1 \ nipreps/mriqc:latest \ /data derivatives/ participant \ -vv --verbose-reports --profile -m T1w --dsname circletests \ --resource-monitor \ --n_procs 2 --ants-nthreads 1 --ants-float \ --webapi-url http://$( hostname -I | awk '{print $1}' )/api/v1 --upload-strict - run: name: Move temporary but relevant artifacts command: | mkdir /tmp/t1w/misc mv /tmp/t1w/work/.resources.*.tsv /tmp/t1w/misc mv /tmp/t1w/work/.resources.*.png /tmp/t1w/misc - store_artifacts: path: /tmp/t1w/misc - save_cache: key: t1w-v6-{{ .Branch }} paths: - /tmp/t1w/work - run: name: Run group-level on T1w images no_output_timeout: 2h command: | docker run -u $( id -u ) --rm -ti \ -v /tmp/data/${TEST_DATA_NAME}:/data:ro \ -v /tmp/t1w:/scratch -w /scratch \ -e MRIQC_DEV=1 \ nipreps/mriqc:latest \ /data derivatives/ group \ -m T1w -vv - store_artifacts: path: /tmp/t1w/derivatives - run: name: Checking presence of outputs command: | mkdir -p /tmp/t1w/test find /tmp/t1w/derivatives | sed s+/tmp/t1w/derivatives/++ | sort > /tmp/t1w/test/outputs.out diff /tmp/src/mriqc/.circleci/circle_T1w.txt /tmp/t1w/test/outputs.out exit $? - run: name: Clean-up work directory (just leave reports & commandlines) command: | find /tmp/t1w/work -type f -not -name "report.rst" -and -not -name "command.txt" -delete - store_artifacts: path: /tmp/t1w/work - run: name: Checking changes on IQMs command: | docker run --rm -ti -v $PWD:/scratch -w /scratch -v /tmp/src:/src \ --entrypoint="dfcheck" nipreps/mriqc:latest \ -i /scratch/derivatives/group_T1w.tsv \ -r /src/mriqc/mriqc/data/testdata/group_T1w.tsv \ || true # ignore failure - run: name: WebAPI - Check records command: | docker run --rm -ti \ --entrypoint="/opt/conda/bin/mriqcwebapi_test" \ nipreps/mriqc:latest \ T1w 4 \ --webapi-url http://$( hostname -I | awk '{print $1}' )/api/v1/T1w - store_artifacts: path: /tmp/t1w/test bold: environment: - TZ: "/usr/share/zoneinfo/America/Los_Angeles" - TEST_DATA_NAME: "circle-tests" - MRIQC_API_DOCKER_IMAGES: "nginx:1.26.2 swaggerapi/swagger-ui:v5.17.14 mongo:6.0.15 python:3.7-slim-bullseye dockereve-master-endpoints:latest" - MIGAS_OPTOUT: "1" machine: # https://discuss.circleci.com/t/linux-machine-executor-images-2021-april-q2-update/39928 # upgrade Docker version image: ubuntu-2204:2023.02.1 working_directory: /tmp/bold steps: - attach_workspace: at: /tmp - run: name: Check whether build should be skipped command: | cd /tmp/src/mriqc if [[ "$( git log --format='format:%s' -n 1 $CIRCLE_SHA1 | grep -i -E '^docs?(\(\w+\))?:' )" != "" ]]; then echo "Only docs build" circleci step halt fi if [[ "$( git log --format=oneline -n 1 $CIRCLE_SHA1 | grep -i -E '\[only[ _]?anat\]' )" != "" ]]; then echo "Only anatomical smoke-tests build" circleci step halt fi - restore_cache: keys: - build-v4-{{ .Branch }}-{{ .Revision }} paths: - /tmp/docker - /tmp/images - run: name: Set-up a Docker registry command: | if [[ -f /tmp/images/registry.tar.gz ]]; then echo "Loading saved registry image" docker load < /tmp/images/registry.tar.gz else echo "Pulling registry image from DockerHub" docker pull registry:2 fi docker run -d --restart=always --name=registry \ -e REGISTRY_HTTP_ADDR=0.0.0.0:5001 -p 5001:5001 \ -v /tmp/docker:/var/lib/registry registry:2 - run: name: Pull images from local registry command: | docker pull localhost:5001/mriqc docker tag localhost:5001/mriqc nipreps/mriqc:latest webapi_images=($MRIQC_API_DOCKER_IMAGES) for image in ${webapi_images[@]}; do docker pull localhost:5001/${image} docker tag localhost:5001/${image} ${image} done; - run: name: Start MRIQC WebAPI endpoint command: | docker-compose -f /tmp/src/mriqcwebapi/dockereve-master/docker-compose.yml --verbose up -d background: true - restore_cache: keys: - data-v2-{{ epoch }} - data-v2- - restore_cache: keys: - bold-v6-{{ .Branch }} - bold-v6-master - bold-v6- - run: name: Remove old, cached configs command: | rm -f /tmp/bold/work/.mriqc.*.toml rm -f /tmp/bold/work/.resources.*.tsv rm -f /tmp/bold/work/.resources.*.png - run: name: Run participant-level on BOLD images no_output_timeout: 2h command: | mkdir -p /tmp/bold/work /tmp/bold/derivatives # Run MRIQC docker run -u $( id -u ) --rm -ti -v /tmp/data/${TEST_DATA_NAME}:/data:ro \ -v $PWD:/scratch -w /scratch \ -e MRIQC_DEV=1 \ nipreps/mriqc:latest \ /data derivatives/ participant \ -vv --verbose-reports --profile -m bold --dsname circletests \ --n_procs 2 --ants-nthreads 1 --ants-float \ --resource-monitor --testing \ --webapi-url http://$( hostname -I | awk '{print $1}' )/api/v1 --upload-strict - run: name: Move temporary but relevant artifacts command: | mkdir /tmp/bold/misc mv /tmp/bold/work/.resources.*.tsv /tmp/bold/misc mv /tmp/bold/work/.resources.*.png /tmp/bold/misc - store_artifacts: path: /tmp/bold/misc - save_cache: key: bold-v6-{{ .Branch }} paths: - /tmp/bold/work - run: name: Run group-level on BOLD images no_output_timeout: 2h command: | docker run -u $( id -u ) --rm -ti -v /tmp/data/${TEST_DATA_NAME}:/data:ro \ -e MRIQC_DEV=1 \ -v $PWD:/scratch -w /scratch \ nipreps/mriqc:latest \ /data derivatives/ group \ -m bold -vv - store_artifacts: path: /tmp/bold/derivatives - run: name: Checking presence of outputs command: | mkdir -p /tmp/bold/test find /tmp/bold/derivatives | sed s+/tmp/bold/derivatives/++ | sort > /tmp/bold/test/outputs.out diff /tmp/src/mriqc/.circleci/circle_bold.txt /tmp/bold/test/outputs.out exit $? - run: name: Clean-up work directory (just leave reports & commandlines) command: | find /tmp/bold/work -type f -not -name "report.rst" -and -not -name "command.txt" -delete - store_artifacts: path: /tmp/bold/work - run: name: Checking changes on IQMs command: | docker run -u $( id -u ) --rm -ti -v /tmp/src:/src -v $PWD:/scratch -w /scratch \ --entrypoint="dfcheck" nipreps/mriqc:latest \ -i /scratch/derivatives/group_bold.tsv \ -r /src/mriqc/mriqc/data/testdata/group_bold.tsv - run: name: WebAPI - Check records command: | docker run --rm -ti \ --entrypoint="/opt/conda/bin/mriqcwebapi_test" \ nipreps/mriqc:latest \ bold 9 \ --webapi-url http://$( hostname -I | awk '{print $1}' )/api/v1/bold - store_artifacts: path: /tmp/bold/test # The resource_class feature allows configuring CPU and RAM resources for each job. Different resource classes are available for different executors. https://circleci.com/docs/2.0/configuration-reference/#resourceclass resource_class: large build_docs: docker: - image: cimg/python:3.10 environment: - FSLOUTPUTTYPE: NIFTI steps: - checkout - run: name: Install Graphviz command: sudo apt update && sudo apt -y install graphviz - run: name: Install deps command: | pip install -U pip hatch docutils hatch version pip install .[docs] - run: name: Build MRIQC documentation no_output_timeout: 2h command: | make -C docs SPHINXOPTS="-W" html | tee $PWD/builddocs.log cat $PWD/builddocs.log grep -qv "ERROR" $PWD/builddocs.log - store_artifacts: path: ./docs/_build/html test_package: docker: - image: cimg/python:3.9 working_directory: /tmp/src/mriqc steps: - checkout - run: name: Start virtual environment command: | python -m venv /tmp/venv source /tmp/venv/bin/activate python -m pip install -U build hatch hatchling pip twine docutils - run: name: Build and check command: | source /tmp/venv/bin/activate python -m build -s -w python -m twine check dist/* - run: name: Validate version command: | source /tmp/venv/bin/activate THISVERSION=$( python3 -m hatch version | tail -n1 | xargs ) python -m pip install dist/*.tar.gz mkdir empty cd empty INSTALLED=$( python -c 'import mriqc; print(mriqc.__version__)' ) test "${CIRCLE_TAG:-$THISVERSION}" == "$INSTALLED" deploy_pypi: docker: - image: cimg/python:3.10 working_directory: /tmp/src/mriqc steps: - checkout - run: name: Start virtual environment command: | python -m venv /tmp/venv source /tmp/venv/bin/activate python -m pip install -U hatch hatchling pip build twine docutils - run: name: Deploy to PyPi command: | source /tmp/venv/bin/activate # Set version on stone before editing the bundle export SETUPTOOLS_SCM_PRETEND_VERSION=$( python -m hatch version | tail -n1 | xargs ) # Inject MRIQC-WebAPI secret if [ "${MRIQC_API_SECRET_TOKEN}" != "" ]; then sed -i -E "s//$MRIQC_API_SECRET_TOKEN/" mriqc/config.py grep -q -i $MRIQC_API_SECRET_TOKEN mriqc/config.py fi python -m build -s -w python -m twine check dist/* python -m twine upload dist/* deploy_docker: machine: image: default working_directory: /tmp/src/mriqc steps: - restore_cache: keys: - build-v4-{{ .Branch }}-{{ .Revision }} paths: - /tmp/docker - /tmp/images - docker/install-docker-credential-helper - run: name: Docker authentication command: | if [[ -n $DOCKER_PAT ]]; then echo "$DOCKER_PAT" | docker login -u $DOCKER_USER --password-stdin fi - run: name: Set-up a Docker registry command: | if [[ -f /tmp/images/registry.tar.gz ]]; then echo "Loading saved registry image" docker load < /tmp/images/registry.tar.gz else echo "Pulling registry image from DockerHub" docker pull registry:2 fi docker run -d -p 5000:5000 --restart=always --name=registry \ -v /tmp/docker:/var/lib/registry registry:2 - run: name: Pull images from local registry command: | docker pull localhost:5000/mriqc docker tag localhost:5000/mriqc nipreps/mriqc:latest - run: name: Deploy to Docker Hub no_output_timeout: 40m command: | # only tag & push latest if CIRCLE_TAG is set if [ -n "${CIRCLE_TAG:-}" ]; then docker push nipreps/mriqc:latest fi docker tag nipreps/mriqc nipreps/mriqc:"${CIRCLE_TAG:=experimental}" docker push nipreps/mriqc:"$CIRCLE_TAG" echo "Pushed tag ${CIRCLE_TAG} to Docker Hub" workflows: version: 2 build_test_deploy: jobs: - build_docs: filters: tags: only: /.*/ - build: context: - nipreps-common filters: branches: ignore: /docs?\/.*/ tags: only: /.*/ - get_data: filters: branches: ignore: /docs?\/.*/ tags: only: /.*/ - test_package: context: - nipreps-common filters: branches: ignore: /docs?\/.*/ tags: only: /.*/ - test_pytest: context: - nipreps-common requires: - build filters: branches: ignore: /docs?\/.*/ tags: only: /.*/ - T1w: requires: - get_data - build filters: branches: ignore: /docs?\/.*/ tags: only: /.*/ - bold: requires: - get_data - build filters: branches: ignore: /docs?\/.*/ tags: only: /.*/ - deploy_docker: context: - nipreps-common requires: - build - test_pytest - test_package - build_docs - T1w - bold filters: branches: only: - master tags: only: /.*/ - deploy_pypi: context: - nipreps-common requires: - deploy_docker filters: branches: ignore: /.*/ tags: only: /.*/ ================================================ FILE: .codecov.yml ================================================ codecov: token: 507d37e9-5aac-4c1e-b28c-f2b4ef9d2edf branch: master bot: oesteban coverage: precision: 2 round: down range: "70...100" status: project: default: target: auto threshold: 5.0 branches: - master patch: default: target: auto branches: - master changes: default: branches: - master ignore: - build/.* - agave/.* - .*/data/.* comment: layout: "header, diff, changes, sunburst, uncovered" branches: - master behavior: default ================================================ FILE: .dockerignore ================================================ # python cache .cache/ __pycache__/**/* __pycache__ *.pyc # python distribution build/**/* build dist/**/* dist mriqc.egg-info/**/* mriqc.egg-info .eggs/**/* .eggs src/**/* src/ notebooks/ .maint/ .maint/**/* # releasing Makefile ### ### git MUST NOT be ignored after moving to multi-staged builds ### ### with the multi-staged build, the strategy is to create a wheel ### in a stage image. To create such a wheel, the git repo is necessary ### for hatch-vcs to interpolate the version and initialize _version.py ### # .github/ # .gitignore # .gitattributes # .git/**/* # .git # Maintenance .circleci/ .maint/ .codecov.yml .coveragerc .pep8speaks.yml .pylintrc .readthedocs.yaml .travis.yml .zenodo.json CONTRIBUTING.md codecov.yml docs docs/**/* long_description.rst notebooks notebooks/**/* requirements-dev.txt requirements.txt venv/ venv/**/* ================================================ FILE: .git_archival.txt ================================================ node: $Format:%H$ node-date: $Format:%cI$ describe-name: $Format:%(describe:tags=true,match=*[0-9]*)$ ref-names: $Format:%D$ ================================================ FILE: .gitattributes ================================================ .git_archival.txt export-subst ================================================ FILE: .github/config.yml ================================================ # Comment to be posted to on pull requests merged by a first time user firstPRMergeComment: > Thanks for opening this pull request and congratulations for taking it to the finish line! It looks like this is your first time contributing to *MRIQC*. :smile: We invite you to list yourself as an *MRIQC* contributor. To learn more about what that entails and how we credit our contributors, please check out the [contributing guidelines](https://www.nipreps.org/community/CONTRIBUTING/#recognizing-contributions). If your name is not already on the list, please insert it, in alphabetical order of (i) lastname and (ii) firstname, into the [``.maint/CONTRIBUTORS.md`` file](https://github.com/nipreps/mriqc/blob/master/.maint/CONTRIBUTORS.md). Of course, if you want to opt-out this time, there is no problem at all with adding your name later. You will be always welcome to add it in the future whenever you feel it should be listed. ================================================ FILE: .github/dependabot.yml ================================================ --- version: 2 updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" ================================================ FILE: .github/workflows/pythonpackage.yml ================================================ # This workflow will install Python dependencies, run tests and lint with a variety of Python versions # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions name: Python package on: push: branches: [ '*' ] tags: [ '*' ] pull_request: branches: [ master, 'maint/*' ] concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true permissions: contents: read jobs: build: if: "!startsWith(github.ref, 'refs/tags/') && !contains(github.event.head_commit.message, '[skip ci]')" runs-on: ubuntu-latest strategy: matrix: python-version: ['3.9', '3.10', '3.11', '3.12', '3.13'] steps: - uses: actions/checkout@v6 with: fetch-depth: 0 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} - name: Build and check package run: | pipx run build pipx run twine check dist/mriqc-* - name: Interpolate version run: | # Interpolate version if [[ "$GITHUB_REF" == refs/tags/* ]]; then TAG=${GITHUB_REF##*/} fi THISVERSION=$( pipx run hatch version | tail -n1 | xargs ) THISVERSION=${TAG:-$THISVERSION} echo "Expected VERSION: \"${THISVERSION}\"" echo "THISVERSION=${THISVERSION}" >> $GITHUB_ENV - name: Install in confined environment [pip] run: | python -m venv /tmp/pip source /tmp/pip/bin/activate python -m pip install . INSTALLED_VERSION=$(python -c 'import mriqc as qc; print(qc.__version__, end="")') echo "INSTALLED: \"${INSTALLED_VERSION}\"" test "${INSTALLED_VERSION}" = "${THISVERSION}" rm -r /tmp/pip - name: Install in confined environment [sdist] run: | python -m venv /tmp/install_sdist source /tmp/install_sdist/bin/activate python -m pip install dist/mriqc*.tar.gz INSTALLED_VERSION=$(python -c 'import mriqc as qc; print(qc.__version__, end="")') echo "INSTALLED: \"${INSTALLED_VERSION}\"" test "${INSTALLED_VERSION}" = "${THISVERSION}" rm -r /tmp/install_sdist - name: Install in confined environment [wheel] run: | python -m venv /tmp/install_wheel source /tmp/install_wheel/bin/activate python -m pip install dist/mriqc*.whl INSTALLED_VERSION=$(python -c 'import mriqc as qc; print(qc.__version__, end="")') echo "INSTALLED: \"${INSTALLED_VERSION}\"" test "${INSTALLED_VERSION}" = "${THISVERSION}" rm -r /tmp/install_wheel ruff: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - run: | pipx run ruff check --output-format=github pipx run ruff format --diff codespell: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: codespell-project/actions-codespell@v2 ================================================ FILE: .gitignore ================================================ # setuptools-scm mriqc/_version.py .DS_Store # IDE configuration .idea/ .vscode # Documentation build docs/build build/ # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] # C extensions *.so # Distribution / packaging .Python env/ venv/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ *.egg-info/ .installed.cfg *.egg # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *,cover # Translations *.mo *.pot # Django stuff *.log # Sphinx documentation docs/_build/ # PyBuilder target/ # Run files work/ ================================================ FILE: .mailmap ================================================ Aaron Piccirilli Adam Huffman Adam Huffman verdurin Adam C. Raikes Adam C. Raikes araikes Adam C. Raikes Adam Raikes Ariel Rokem Asier Erramuzpe Asier Erramuzpe erramuzpe Bennet Fauber Bennet Fauber justbennet Céline Provins Céline Provins cprovins Céline Provins celprov Céline Provins <77437752+celprov@users.noreply.github.com> Christopher J. Markiewicz Christopher J. Markiewicz Christopher J. Markiewicz Conrad Ma Conrad Ma Conrad Conrad Ma 394822740 Conrad Ma <394822740@qq.com> Conrad Ma dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Dimitri Papadopoulos <3234522+DimitriPapadopoulos@users.noreply.github.com> Dylan Nielson Dylan Nielson Dylan Elodie Savary Elodie Savary esavary James D. Kent James D. Kent James D. Kent jdkent James D. Kent Fred Mertz Johannes Achtzehn Johannes Achtzehn jAchtzehn John A. Lee John A. Lee john anthony lee John A. Lee leej3 Krzysztof J. Gorgolewski Krzysztof J. Gorgolewski Krzysztof J. Gorgolewski Krzysztof J. Gorgolewski Krzysztof J. Gorgolewski Krzysztof J. Gorgolewski Krzysztof J. Gorgolewski Mathias Goncalves Mathias Goncalves mathiasg Mathias Goncalves Mathias Goncalves McKenzie P. Hagen McKenzie P. Hagen mckenziephagen Michael Dayan Michael G. Clark Michael G. Clark mgclark Michael G. Clark Michael Clark Michael Krause Michael Krause octomike NiPreps Bot NiPreps Bot nipreps-bot Pablo Velasco Pablo Velasco pvelasco Patrick Sadil Patrick Sadil psadil Oscar Esteban Oscar Esteban Ross Blair Ross Blair Teresa Gomez <46339554+teresamg@users.noreply.github.com> Teresa Gomez <46339554+teresamg@users.noreply.github.com> teresamg Yibei Chen Victor Férat Victor Férat vferat William Triplett Zvi Baratz Zvi Baratz ZviBaratz Zvi Baratz zvi-quantivly Zvi Baratz <133042121+zvi-quantivly@users.noreply.github.com> Zvi Baratz ================================================ FILE: .maint/CONTRIBUTORS.md ================================================ # CONTRIBUTORS This document lists those who have made contributions to the Project. As per the contributor guidelines, they should be included by default in any publications derived from the Project. If you are new to the project, don't forget to add your name and affiliation to the list of contributors here! Our Welcome Bot will send an automated message reminding this to first-time contributors. Before every release, unlisted contributors will be invited again to add their names to this file (just in case they missed the automated message from our Welcome Bot). | **Lastname** | **Name** | **Handle** | **ORCID** | **Affiliation** | | --- | --- | --- | --- | --- | | Achtzehn | Johannes | @jAchtzehn | 0000-0002-0657-6516 | Charité Berlin, Berlin, Germany | | Baratz | Zvi | @zvi-quantivly | 0000-0001-7159-1387 | Quantivly Inc., Somerville, MA, USA | | Beliy | Nikita | @nbeliy | | CRC ULiege, Liege, Belgium | | Birman | Daniel | @dbirman | 0000-0003-3748-6289 | Department of Psychology, Stanford University, CA, USA | | Blair | Ross W. | @rwblair | 0000-0003-3007-1056 | Department of Psychology, Stanford University, CA, USA | | Chen | Yibei | @yibeichan | 0000-0003-2882-0900 | McGovern Institute for Brain Research, Massachusetts Institute of Technology, Cambridge, USA | | Clark | Michael G. | @mgclark | | National Institutes of Health, USA | | Dayan | Michael | @neurorepro | 0000-0002-2666-0969 | International Committee of the Red Cross - ICRC, Geneva, Switzerland | | Durnez | Joke | @jokedurnez | 0000-0001-9030-2202 | Department of Psychology, Stanford University, CA, USA | | Erramuzpe | Asier | @erramuzpe | 0000-0002-9402-2184 | Computational Neuroimaging Lab, BioCruces Health Research Institute | | Fauber | Bennet | @justbennet | | University of Michigan, Ann Arbor, USA | | Férat | Victor | @vferat | 0000-0003-1952-7657 | Department of Basic Neurosciences, Université de Genève, Geneva, Switzerland | | Garcia-Dias | Rafael | @garciadias | 0000-0001-9332-1580 | Institute of Psychiatry, Psychology & Neuroscience, King's College London, London, UK | | Ghosh | Satrajit S. | @satra | 0000-0002-5312-6729 | McGovern Institute for Brain Research, MIT, MA, USA; and Department of Otolaryngology, Harvard Medical School, MA, USA | | Gomez | Teresa | @teresamg | | The University of Washington eScience Institute, WA, USA | | Goncalves | Mathias | @mgxd | 0000-0002-7252-7771 | Department of Psychology, Stanford University, CA, USA | | Halchenko | Yaroslav O. | @yarikoptic | 0000-0003-3456-2493 | Psychological and Brain Sciences Department, Dartmouth College, NH, USA | | Huffman | Adam | @verdurin | | Department of Physics, Imperial College London, London, UK | | Kay | Benjamin | @benkay86 | | Washington University School of Medicine, St.Louis, MO, USA | | Kent | James D. | @jdkent | 0000-0002-4892-2659 | Department of Psychology, University of Texas at Austin, TX, USA | | Krause | Michael | @octomike | | Max Planck Institute for Human Development, Berlin, Germany | | Lee | John A. | @leej3 | 0000-0001-5884-4247 | Quansight, Dublin, Ireland | | Legarreta Gorroño | Jon Haitz | @jhlegarreta | 0000-0002-9661-1396 | Brigham and Women's Hospital, Mass General Brigham, Harvard Medical School, MA, USA | | Nichols | Thomas | @nicholst | 0000-0002-4516-5103 | Oxford Big Data Institute, University of Oxford, Oxford, GB | | Nielson | Dylan | @Shotgunosine | 0000-0003-4613-6643 | Section on Clinical and Computational Psychiatry, National Institute of Mental Health, Bethesda, MD, USA | | Papadopoulos Orfanos | Dimitri | @DimitriPapadopoulos | 0000-0002-1242-8990 | NeuroSpin, CEA, Université Paris-Saclay, NeuroSpin, Gif-sur-Yvette, France | | Raikes | Adam C. | @araikes | | Center for Innovation in Brain Science, University of Arizona, Tucson, AZ, USA | | Rokem | Ariel | @arokem | 0000-0003-0679-1985 | The University of Washington eScience Institute, WA, USA | | Sadil | Patrick | @psadil | 0000-0003-4141-1343 | Johns Hopkins Bloomberg School of Public Health, MD, USA | | Salo | Taylor | @tsalo | 0000-0001-9813-3167 | Department of Psychology, Florida International University, FL, USA | | Savary | Elodie | @esavary | 0000-0002-3896-6906 | Department of Radiology, Lausanne University Hospital and University of Lausanne, Switzerland | | Tooley | Ursula A. | @utooley | 0000-0001-6377-3885 | Department of Neuroscience, University of Pennsylvania, PA, USA | | Triplett | William | @wtriplett | 0000-0002-9546-1306 | University of Florida: Gainesville, Florida, US | | Varada | Jan | @jvarada | | Functional MRI Facility, National Institute of Mental Health, Bethesda, MD, USA | | Velasco | Pablo | @pvelasco | 0000-0002-5749-6049 | Center for Brain Imaging, New York University, NY, USA | ================================================ FILE: .maint/FORMER.md ================================================ # FORMER MEMBERS This document lists former contributors or maintainers who want to disengage from the Project, and seek to be dismissed in communications or future papers. By adding your name to this list you are giving up on all your responsibilities to the project. Should you desire to be considered back as a contributor or maintainer, please remove your name from this list and proceed as prescribed in the governance documents. | **Lastname** | **Name** | **Handle** | | --- | --- | --- | | Bot | NiPreps | @nipreps-bot | | dependabot[bot] | | @dependabot | | Ma | Conrad | @394822740 | | Piccirilli | Aaron | @apiccirilli | | Gorgolewski | Krzysztof J. | @chrisgorgo | ================================================ FILE: .maint/MAINTAINERS.md ================================================ # Maintainers This document lists the Maintainers of the project. Maintainers may be added once approved by the existing maintainers as described in the `../GOVERNANCE.md` document. By adding your name to this list you are agreeing to abide by the governance documents and to abide by all of the Organization's polices, including the code of conduct, trademark policy, and antitrust policy. If you are participating because of your affiliation with another organization (designated below), you represent that you have the authority to bind that organization to these policies. | **Lastname** | **Name** | **Handle** | **ORCID** | **Affiliation** | | --- | --- | --- | --- | --- | | Provins | Céline | @celprov | 0000-0002-1668-9629 | Department of Radiology, Lausanne University Hospital and University of Lausanne, Switzerland | | Hagen | McKenzie P. | @mckenziephagen | 0000-0002-7454-8189 | Psychology Department, University of Washington, Seattle, WA, USA | | MacNicol | Eilidh | @eilidhmacnicol | 0000-0003-3715-7012 | Department of Neuroimaging, Institute of Psychiatry, Psychology and Neuroscience, King's College London, London, UK | | Esteban | Oscar | @oesteban | 0000-0001-8435-6191 | Lausanne University Hospital and University of Lausanne, Lausanne, Switzerland | | Markiewicz | Christopher J. | @effigies | 0000-0002-6533-164X | Department of Psychology, Stanford University, CA, USA | ================================================ FILE: .maint/PIs.md ================================================ # PRINCIPAL INVESTIGATORS This documents the key personnel who oversees the development of the Project and secures funding. The names in this file are designated by the Organization's TSC at the time of accepting the Project under the Organization. Changes to this file must be approved by the TSC. When a PI ceases serving as such, by default, their name will be placed in the `CONTRIBUTORS.md` file should it not be there already. By having your name in this list you are agreeing to abide by the governance documents and to abide by all of the Organization's polices, including the code of conduct, trademark policy, and antitrust policy. If you are participating because of your affiliation with another organization (designated below), you represent that you have the authority to bind that organization to these policies. | **Lastname** | **Name** | **Handle** | **ORCID** | **Affiliation** | | --- | --- | --- | --- | --- | | Esteban | Oscar | @oesteban | 0000-0001-8435-6191 | Department of Radiology, Lausanne University Hospital and University of Lausanne, Switzerland | | Rokem | Ariel | @arokem | 0000-0003-0679-1985 | The University of Washington eScience Institute, WA, USA | | Poldrack | Russell A. | @poldrack | 0000-0001-6755-0259 | Department of Psychology, Stanford University, CA, USA | | Thomas | Adam G. | | 0000-0002-2850-1419 | Data Science and Sharing Team, National Institute of Mental Health, Bethesda, MD, USA | ================================================ FILE: .maint/ROADMAP.md ================================================ # ROADMAP This document states how the roadmap is built, discussed and monitored by the Maintainers, with the engagement of Contributors and PIs. For example, if the GitHub Projects feature is used, this document will contain a link to the corresponding Project and document who is responsible of managing the project, contacting Contributors and Maintainers to monitor the progress of activities, organizing meetings and discussions around the activities, etc. This document may also indicate how the *milestones* feature of the project is used and, as for Projects, who is responsible of what coordination actions, their frequence, etc. ================================================ FILE: .maint/requirements.txt ================================================ click fuzzywuzzy python-Levenshtein ================================================ FILE: .maint/update_authors.py ================================================ #!/usr/bin/env python3 """Update and sort the creators list of the zenodo record.""" import json import sys from pathlib import Path import click from fuzzywuzzy import fuzz, process def read_md_table(md_text): """ Extract the first table found in a markdown document as a Python dict. Examples -------- >>> read_md_table(''' ... # Some text ... ... More text ... ... | **Header1** | **Header2** | ... | --- | --- | ... | val1 | val2 | ... | | val4 | ... ... | **Header3** | **Header4** | ... | --- | --- | ... | val1 | val2 | ... | | val4 | ... ''') [{'header1': 'val1', 'header2': 'val2'}, {'header2': 'val4'}] """ prev = None keys = None retval = [] for line in md_text.splitlines(): if line.strip().startswith('| --- |'): keys = ( k.replace('*', '').strip() for k in prev.split('|') ) keys = [k.lower() for k in keys if k] continue elif not keys: prev = line continue if not line or not line.strip().startswith('|'): break values = [v.strip() or None for v in line.split('|')][1:-1] retval.append({k: v for k, v in zip(keys, values) if v}) return retval def sort_contributors(entries, git_lines, exclude=None, last=None): """Return a list of author dictionaries, ordered by contribution.""" last = last or [] sorted_authors = sorted(entries, key=lambda i: i['name']) first_last = [ ' '.join(val['name'].split(',')[::-1]).strip() for val in sorted_authors ] first_last_excl = [ ' '.join(val['name'].split(',')[::-1]).strip() for val in exclude or [] ] unmatched = [] author_matches = [] for ele in git_lines: matches = process.extract( ele, first_last, scorer=fuzz.token_sort_ratio, limit=2 ) # matches is a list [('First match', % Match), ('Second match', % Match)] if matches[0][1] > 80: val = sorted_authors[first_last.index(matches[0][0])] else: # skip unmatched names if ele not in first_last_excl: unmatched.append(ele) continue if val not in author_matches: author_matches.append(val) names = {' '.join(val['name'].split(',')[::-1]).strip() for val in author_matches} for missing_name in first_last: if missing_name not in names: missing = sorted_authors[first_last.index(missing_name)] author_matches.append(missing) position_matches = [] for i, item in enumerate(author_matches): pos = item.pop('position', None) if pos is not None: position_matches.append((i, int(pos))) for i, pos in position_matches: if pos < 0: pos += len(author_matches) + 1 author_matches.insert(pos, author_matches.pop(i)) return author_matches, unmatched def get_git_lines(fname='line-contributors.txt'): """Run git-line-summary.""" import shutil import subprocess as sp contrib_file = Path(fname) lines = [] if contrib_file.exists(): print('WARNING: Reusing existing line-contributors.txt file.', file=sys.stderr) lines = contrib_file.read_text().splitlines() git_line_summary_path = shutil.which('git-line-summary') if not git_line_summary_path: git_line_summary_path = 'git summary --dedup-by-email'.split(' ') else: git_line_summary_path = [git_line_summary_path] if not lines and git_line_summary_path: print('Running git-line-summary on repo') lines = sp.check_output(git_line_summary_path).decode().splitlines() lines = [line for line in lines if 'Not Committed Yet' not in line] contrib_file.write_text('\n'.join(lines)) if not lines: _msg = ( ': git-line-summary not found, please install git-extras ' * (git_line_summary_path is None) ) raise RuntimeError( f'Could not find line-contributors from git repository{_msg}.' ) return [' '.join(line.strip().split()[1:-1]) for line in lines if '%' in line] def _namelast(inlist): retval = [] for i in inlist: i['name'] = (f"{i.pop('name', '')} {i.pop('lastname', '')}").strip() if not i['name']: i['name'] = i.get('handle', '') retval.append(i) return retval @click.group() def cli(): """Generate authorship boilerplates.""" pass @cli.command() @click.option('-z', '--zenodo-file', type=click.Path(exists=True), default='.zenodo.json') @click.option('-m', '--maintainers', type=click.Path(exists=True), default='.maint/MAINTAINERS.md') @click.option('-c', '--contributors', type=click.Path(exists=True), default='.maint/CONTRIBUTORS.md') @click.option('--pi', type=click.Path(exists=True), default='.maint/PIs.md') @click.option('-f', '--former-file', type=click.Path(exists=True), default='.maint/FORMER.md') def zenodo( zenodo_file, maintainers, contributors, pi, former_file, ): """Generate a new Zenodo payload file.""" data = get_git_lines() zenodo = json.loads(Path(zenodo_file).read_text()) former = _namelast(read_md_table(Path(former_file).read_text())) zen_creators, miss_creators = sort_contributors( _namelast(read_md_table(Path(maintainers).read_text())), data, exclude=former, ) zen_contributors, miss_contributors = sort_contributors( _namelast(read_md_table(Path(contributors).read_text())), data, exclude=former ) zen_pi = _namelast(reversed(read_md_table(Path(pi).read_text()))) zenodo['creators'] = zen_creators zenodo['contributors'] = zen_contributors + [ pi for pi in zen_pi if pi not in zen_contributors ] creator_names = { c['name'] for c in zenodo['creators'] if c['name'] != '' } zenodo['contributors'] = [ c for c in zenodo['contributors'] if c['name'] not in creator_names ] misses = set(miss_creators).intersection(miss_contributors) if misses: print( "Some people made commits, but are missing in .maint/ " f"files: {', '.join(misses)}", file=sys.stderr, ) # Remove position for creator in zenodo['creators']: creator.pop('position', None) creator.pop('handle', None) if 'affiliation' not in creator: creator['affiliation'] = 'Unknown affiliation' elif isinstance(creator['affiliation'], list): creator['affiliation'] = creator['affiliation'][0] for creator in zenodo['contributors']: creator.pop('handle', None) creator['type'] = 'Researcher' creator.pop('position', None) if 'affiliation' not in creator: creator['affiliation'] = 'Unknown affiliation' elif isinstance(creator['affiliation'], list): creator['affiliation'] = creator['affiliation'][0] Path(zenodo_file).write_text( f'{json.dumps(zenodo, indent=2)}\n' ) @cli.command() @click.option('-m', '--maintainers', type=click.Path(exists=True), default='.maint/MAINTAINERS.md') @click.option('-c', '--contributors', type=click.Path(exists=True), default='.maint/CONTRIBUTORS.md') @click.option('--pi', type=click.Path(exists=True), default='.maint/PIs.md') @click.option('-f', '--former-file', type=click.Path(exists=True), default='.maint/FORMER.md') def publication( maintainers, contributors, pi, former_file, ): """Generate the list of authors and affiliations for papers.""" members = ( _namelast(read_md_table(Path(maintainers).read_text())) + _namelast(read_md_table(Path(contributors).read_text())) ) former_names = _namelast(read_md_table(Path(former_file).read_text())) hits, misses = sort_contributors( members, get_git_lines(), exclude=former_names, ) pi_hits = _namelast(reversed(read_md_table(Path(pi).read_text()))) pi_names = [pi['name'] for pi in pi_hits] hits = [ hit for hit in hits if hit['name'] not in pi_names ] + pi_hits def _aslist(value): if isinstance(value, (list, tuple)): return value return [value] # Remove position affiliations = [] for item in hits: item.pop('position', None) for a in _aslist(item.get('affiliation', 'Unaffiliated')): if a not in affiliations: affiliations.append(a) aff_indexes = [ ', '.join( [ '%d' % (affiliations.index(a) + 1) for a in _aslist(author.get('affiliation', 'Unaffiliated')) ] ) for author in hits ] if misses: print( "Some people made commits, but are missing in .maint/ " f"files: {', '.join(misses)}", file=sys.stderr, ) print('Authors (%d):' % len(hits)) print( '{}.'.format('; '.join( [ '{} \\ :sup:`{}`\\ '.format(i['name'], idx) for i, idx in zip(hits, aff_indexes) ] )) ) print( '\n\nAffiliations:\n{}'.format('\n'.join( [f'{i + 1: >2}. {a}' for i, a in enumerate(affiliations)] )) ) if __name__ == '__main__': """ Install entry-point """ cli() ================================================ FILE: .maint/update_changes.sh ================================================ #!/bin/bash # # Collects the pull-requests since the latest release and # aranges them in the CHANGES.rst file. # # This is a script to be run before releasing a new version. # # Usage /bin/bash update_changes.sh 1.0.1 # # Setting # $ help set set -u # Treat unset variables as an error when substituting. set -x # Print command traces before executing command. # Check whether the Upcoming release header is present head -1 CHANGES.rst | grep -q Upcoming UPCOMING=$? if [[ "$UPCOMING" == "0" ]]; then head -n3 CHANGES.rst >> newchanges fi # Search for PRs since previous release git show --pretty='format: * %b %s' HEAD | sed 's/Merge pull request \#\([^\d]*\)\ from\ .*/(\#\1)/' >> newchanges # Add back the Upcoming header if it was present if [[ "$UPCOMING" == "0" ]]; then tail -n+4 CHANGES.rst >> newchanges else cat CHANGES.rst >> newchanges fi # Replace old CHANGES.rst with new file mv newchanges CHANGES.rst ================================================ FILE: .readthedocs.yaml ================================================ # .readthedocs.yaml # Read the Docs configuration file # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details # Required version: 2 # Set the version of Python and other tools you might need build: os: ubuntu-20.04 apt_packages: - graphviz tools: python: "3.9" # Build documentation in the docs/ directory with Sphinx sphinx: configuration: docs/source/conf.py # Install with pip install .[docs] python: install: - method: pip path: . extra_requirements: - docs ================================================ FILE: .zenodo.json ================================================ { "title": "MRIQC: Advancing the automatic prediction of image quality in MRI from unseen sites", "description": "

Automated Quality Control and visual reports for Quality Assessment of structural (T1w, T2w) and functional MRI of the brain http://mriqc.readthedocs.io.

", "creators": [ { "orcid": "0000-0001-8435-6191", "affiliation": "Lausanne University Hospital and University of Lausanne, Lausanne, Switzerland", "name": "Oscar Esteban" }, { "orcid": "0000-0002-6533-164X", "affiliation": "Department of Psychology, Stanford University, CA, USA", "name": "Christopher J. Markiewicz" }, { "orcid": "0000-0003-3715-7012", "affiliation": "Department of Neuroimaging, Institute of Psychiatry, Psychology and Neuroscience, King's College London, London, UK", "name": "Eilidh MacNicol" }, { "orcid": "0000-0002-1668-9629", "affiliation": "Department of Radiology, Lausanne University Hospital and University of Lausanne, Switzerland", "name": "C\u00e9line Provins" }, { "orcid": "0000-0002-7454-8189", "affiliation": "Psychology Department, University of Washington, Seattle, WA, USA", "name": "McKenzie P. Hagen" } ], "contributors": [ { "orcid": "0000-0001-7159-1387", "affiliation": "Quantivly Inc., Somerville, MA, USA", "name": "Zvi Baratz", "type": "Researcher" }, { "affiliation": "The University of Washington eScience Institute, WA, USA", "name": "Teresa Gomez", "type": "Researcher" }, { "orcid": "0000-0003-4613-6643", "affiliation": "Section on Clinical and Computational Psychiatry, National Institute of Mental Health, Bethesda, MD, USA", "name": "Dylan Nielson", "type": "Researcher" }, { "affiliation": "Functional MRI Facility, National Institute of Mental Health, Bethesda, MD, USA", "name": "Jan Varada", "type": "Researcher" }, { "orcid": "0000-0003-3007-1056", "affiliation": "Department of Psychology, Stanford University, CA, USA", "name": "Ross W. Blair", "type": "Researcher" }, { "orcid": "0000-0003-0679-1985", "affiliation": "The University of Washington eScience Institute, WA, USA", "name": "Ariel Rokem", "type": "Researcher" }, { "orcid": "0000-0002-1242-8990", "affiliation": "NeuroSpin, CEA, Universit\u00e9 Paris-Saclay, NeuroSpin, Gif-sur-Yvette, France", "name": "Dimitri Papadopoulos Orfanos", "type": "Researcher" }, { "orcid": "0000-0002-9546-1306", "affiliation": "University of Florida: Gainesville, Florida, US", "name": "William Triplett", "type": "Researcher" }, { "orcid": "0000-0002-7252-7771", "affiliation": "Department of Psychology, Stanford University, CA, USA", "name": "Mathias Goncalves", "type": "Researcher" }, { "affiliation": "CRC ULiege, Liege, Belgium", "name": "Nikita Beliy", "type": "Researcher" }, { "orcid": "0000-0001-5884-4247", "affiliation": "Quansight, Dublin, Ireland", "name": "John A. Lee", "type": "Researcher" }, { "orcid": "0000-0002-9661-1396", "affiliation": "Brigham and Women's Hospital, Mass General Brigham, Harvard Medical School, MA, USA", "name": "Jon Haitz Legarreta Gorro\u00f1o", "type": "Researcher" }, { "orcid": "0000-0003-4141-1343", "affiliation": "Johns Hopkins Bloomberg School of Public Health, MD, USA", "name": "Patrick Sadil", "type": "Researcher" }, { "orcid": "0000-0001-6377-3885", "affiliation": "Department of Neuroscience, University of Pennsylvania, PA, USA", "name": "Ursula A. Tooley", "type": "Researcher" }, { "orcid": "0000-0003-3456-2493", "affiliation": "Psychological and Brain Sciences Department, Dartmouth College, NH, USA", "name": "Yaroslav O. Halchenko", "type": "Researcher" }, { "orcid": "0000-0003-2882-0900", "affiliation": "McGovern Institute for Brain Research, Massachusetts Institute of Technology, Cambridge, USA", "name": "Yibei Chen", "type": "Researcher" }, { "orcid": "0000-0002-4892-2659", "affiliation": "Department of Psychology, University of Texas at Austin, TX, USA", "name": "James D. Kent", "type": "Researcher" }, { "affiliation": "University of Michigan, Ann Arbor, USA", "name": "Bennet Fauber", "type": "Researcher" }, { "orcid": "0000-0001-9813-3167", "affiliation": "Department of Psychology, Florida International University, FL, USA", "name": "Taylor Salo", "type": "Researcher" }, { "affiliation": "Max Planck Institute for Human Development, Berlin, Germany", "name": "Michael Krause", "type": "Researcher" }, { "orcid": "0000-0002-5749-6049", "affiliation": "Center for Brain Imaging, New York University, NY, USA", "name": "Pablo Velasco", "type": "Researcher" }, { "orcid": "0000-0002-4516-5103", "affiliation": "Oxford Big Data Institute, University of Oxford, Oxford, GB", "name": "Thomas Nichols", "type": "Researcher" }, { "affiliation": "Department of Physics, Imperial College London, London, UK", "name": "Adam Huffman", "type": "Researcher" }, { "orcid": "0000-0002-3896-6906", "affiliation": "Department of Radiology, Lausanne University Hospital and University of Lausanne, Switzerland", "name": "Elodie Savary", "type": "Researcher" }, { "orcid": "0000-0002-0657-6516", "affiliation": "Charit\u00e9 Berlin, Berlin, Germany", "name": "Johannes Achtzehn", "type": "Researcher" }, { "orcid": "0000-0001-9030-2202", "affiliation": "Department of Psychology, Stanford University, CA, USA", "name": "Joke Durnez", "type": "Researcher" }, { "orcid": "0000-0002-5312-6729", "affiliation": "McGovern Institute for Brain Research, MIT, MA, USA; and Department of Otolaryngology, Harvard Medical School, MA, USA", "name": "Satrajit S. Ghosh", "type": "Researcher" }, { "affiliation": "Center for Innovation in Brain Science, University of Arizona, Tucson, AZ, USA", "name": "Adam C. Raikes", "type": "Researcher" }, { "orcid": "0000-0002-9402-2184", "affiliation": "Computational Neuroimaging Lab, BioCruces Health Research Institute", "name": "Asier Erramuzpe", "type": "Researcher" }, { "affiliation": "Washington University School of Medicine, St.Louis, MO, USA", "name": "Benjamin Kay", "type": "Researcher" }, { "orcid": "0000-0003-3748-6289", "affiliation": "Department of Psychology, Stanford University, CA, USA", "name": "Daniel Birman", "type": "Researcher" }, { "orcid": "0000-0002-2666-0969", "affiliation": "International Committee of the Red Cross - ICRC, Geneva, Switzerland", "name": "Michael Dayan", "type": "Researcher" }, { "affiliation": "National Institutes of Health, USA", "name": "Michael G. Clark", "type": "Researcher" }, { "orcid": "0000-0001-9332-1580", "affiliation": "Institute of Psychiatry, Psychology & Neuroscience, King's College London, London, UK", "name": "Rafael Garcia-Dias", "type": "Researcher" }, { "orcid": "0000-0002-2850-1419", "affiliation": "Data Science and Sharing Team, National Institute of Mental Health, Bethesda, MD, USA", "name": "Adam G. Thomas", "type": "Researcher" }, { "orcid": "0000-0001-6755-0259", "affiliation": "Department of Psychology, Stanford University, CA, USA", "name": "Russell A. Poldrack", "type": "Researcher" } ], "keywords": [ "neuroimaging", "workflow", "pipeline", "quality-control", "fMRI", "BIDS" ], "related_identifiers": [ { "identifier": "http://mriqc.readthedocs.io", "relation": "documents", "scheme": "url" }, { "identifier": "10.1371/journal.pone.0184661", "relation": "isPartOf", "scheme": "doi" } ], "license": "Apache-2.0", "upload_type": "software" } ================================================ FILE: AGENTS.md ================================================ # Agent Instructions for Codex ## Operating Principles 1. **Always plan first.** Provide a brief plan before any changes and do the hardest thinking during the planning phase. 2. **Highlight critical points.** When proposing tasks, call out steps that could introduce side effects (e.g., migrations, API changes, backward-compatibility concerns). 3. **Ask one question at a time.** If a decision is needed from a user, ask a single, clear question before proceeding. ## Branch Naming - **Format:** `/` - **Examples:** - `fix/one-off-bug-gh134` - `enh/add-bids-validator` - `doc/update-readme` - If working from a tracked issue, include the issue number in the branch name (e.g., `fix/segfault-gh134`). ## Commit Messages (Conventional Commits) - **Must strictly follow** Conventional Commits: https://www.conventionalcommits.org/en/v1.0.0/ - Examples: - `fix(parser): handle empty token stream` - `docs(readme): add quickstart example` - `chore(ci): pin action versions` ## Pull Request Titles & Descriptions - **PR Title format:** `TYPE: Short summary` - Type is **all caps**, from a conventional-commit-like set (e.g., FIX, ENH, MNT, DOC, CI, CHORE). - **No scope parenthetical** in PR titles. - **Examples:** - `FIX: Address one-off bug in traversing list` - `DOC: Complete docstring with missing arguments` - **PR Description should include:** - Summary of changes - Motivation / context - Testing performed (exact commands) ## Linting & Pre-Change Validation - **Always run linting before proposing changes.** - This repository uses the GitHub Actions linting command: - `pipx run ruff format --diff` - If linting cannot run due to environment constraints, state the limitation and suggest a retry in a fully configured environment. ================================================ FILE: CHANGES.rst ================================================ 25.0.0 (TBD) ============ A new 25.x series. CHANGES ------- * FIX: Decode bytes to produce a string (#1371) * FIX: Containers report wrong version (#1357) * FIX: Elapsed run time format (#1366) * FIX: Passing ``bytes`` into ``str.join()`` (#1356) * FIX: Optimize interface to minimize memory fingerprint (#1351) * ENH: Remove redundant *DIPY* function (#1369) * DOC: Add a HPC troubleshooting section to the documentation (#1349) * DOC: Provide a range of years ending with current for the copyright statement (#1359) * DOC: Fix missing code block start (#1368) * DOC: Reorganize documentation and redirect to *NiPreps* docs (#1367) 24.0.2 (August 26, 2024) ======================== A patch release with bugfixes and enhancements. CHANGES ------- * FIX: Pin latest *NiReports* release (24.0.2) addressing ``fMRIPlot`` issues by @oesteban (`#1342 `__) * FIX: Edge artifacts in first and last slices due to interpolation by @oesteban (`#1338 `__) * FIX: Normalize bids-filters' modality keys to be lowercase by @oesteban (`#1332 `__) * ENH: Add license NOTICE to start banner by @oesteban (`#1343 `__) * ENH: Enable writing crashfiles in compressed-pickle format by @oesteban (`#1339 `__) * ENH: Use ``orjson`` to serialize JSON, addressing *Numpy* serialization issues by @oesteban (`#1337 `__) * ENH: Handle WebAPI timeouts more gently by @oesteban (`#1336 `__) 24.0.1 (August 20, 2024) ======================== A patch release with a large number of bugfixes (mostly focusing on memory issues), maintenance activities, and metadata crawling before *Nipype* kicks in as a major optimization. With thanks to @jhlegarreta for his first contribution in `#1293 __`. .. admonition:: Author list for papers based on *MRIQC* 24.0 series As described in the `Contributor Guidelines `__, anyone listed as a developer or contributor may write and submit manuscripts about *MRIQC*. To do so, please move the author(s) name(s) to the front of the following list: Christopher J. Markiewicz \ :sup:`1`\ ; Zvi Baratz \ :sup:`2`\ ; Eilidh MacNicol \ :sup:`3`\ ; Céline Provins \ :sup:`4`\ ; Teresa Gomez \ :sup:`5`\ ; Dylan Nielson \ :sup:`6`\ ; Ross W. Blair \ :sup:`1`\ ; Jan Varada \ :sup:`7`\ ; Dimitri Papadopoulos Orfanos \ :sup:`8`\ ; William Triplett \ :sup:`9`\ ; Mathias Goncalves \ :sup:`1`\ ; Nikita Beliy \ :sup:`10`\ ; John A. Lee \ :sup:`11`\ ; Yibei Chen \ :sup:`12`\ ; Ursula A. Tooley \ :sup:`13`\ ; Patrick Sadil \ :sup:`14`\ ; Yaroslav O. Halchenko \ :sup:`15`\ ; James D. Kent \ :sup:`16`\ ; Taylor Salo \ :sup:`17`\ ; Bennet Fauber \ :sup:`18`\ ; Thomas Nichols \ :sup:`19`\ ; Pablo Velasco \ :sup:`20`\ ; Michael Krause \ :sup:`21`\ ; Jon Haitz Legarreta Gorroño \ :sup:`22`\ ; Satrajit S. Ghosh \ :sup:`23`\ ; Joke Durnez \ :sup:`1`\ ; Johannes Achtzehn \ :sup:`24`\ ; Elodie Savary \ :sup:`4`\ ; Adam Huffman \ :sup:`25`\ ; Rafael Garcia-Dias \ :sup:`26`\ ; Michael G. Clark \ :sup:`27`\ ; Michael Dayan \ :sup:`28`\ ; McKenzie P. Hagen \ :sup:`29`\ ; Daniel Birman \ :sup:`1`\ ; Benjamin Kay \ :sup:`30`\ ; Asier Erramuzpe \ :sup:`31`\ ; Adam C. Raikes \ :sup:`32`\ ; Adam G. Thomas \ :sup:`33`\ ; Russell A. Poldrack \ :sup:`1`\ ; Ariel Rokem \ :sup:`5`\ ; Oscar Esteban \ :sup:`4`\ . Affiliations: 1. Department of Psychology, Stanford University, CA, USA 2. Quantivly Inc., Somerville, MA, USA 3. Department of Neuroimaging, Institute of Psychiatry, Psychology and Neuroscience, King's College London, London, UK 4. Department of Radiology, Lausanne University Hospital and University of Lausanne, Switzerland 5. The University of Washington eScience Institute, WA, USA 6. Section on Clinical and Computational Psychiatry, National Institute of Mental Health, Bethesda, MD, USA 7. Functional MRI Facility, National Institute of Mental Health, Bethesda, MD, USA 8. NeuroSpin, CEA, Université Paris-Saclay, NeuroSpin, Gif-sur-Yvette, France 9. University of Florida: Gainesville, Florida, US 10. CRC ULiege, Liege, Belgium 11. Quansight, Dublin, Ireland 12. McGovern Institute for Brain Research, Massachusetts Institute of Technology, Cambridge, USA 13. Department of Neuroscience, University of Pennsylvania, PA, USA 14. Johns Hopkins Bloomberg School of Public Health, MD, USA 15. Psychological and Brain Sciences Department, Dartmouth College, NH, USA 16. Department of Psychology, University of Texas at Austin, TX, USA 17. Department of Psychology, Florida International University, FL, USA 18. University of Michigan, Ann Arbor, USA 19. Oxford Big Data Institute, University of Oxford, Oxford, GB 20. Center for Brain Imaging, New York University, NY, USA 21. Max Planck Institute for Human Development, Berlin, Germany 22. Brigham and Women's Hospital, Mass General Brigham, Harvard Medical School, MA, USA 23. McGovern Institute for Brain Research, MIT, MA, USA; and Department of Otolaryngology, Harvard Medical School, MA, USA 24. Charité Berlin, Berlin, Germany 25. Department of Physics, Imperial College London, London, UK 26. Institute of Psychiatry, Psychology & Neuroscience, King's College London, London, UK 27. National Institutes of Health, USA 28. International Committee of the Red Cross - ICRC, Geneva, Switzerland 29. Psychology Department, University of Washington, Seattle, WA, USA 30. Washington University School of Medicine, St.Louis, MO, USA 31. Computational Neuroimaging Lab, BioCruces Health Research Institute 32. Center for Innovation in Brain Science, University of Arizona, Tucson, AZ, USA 33. Data Science and Sharing Team, National Institute of Mental Health, Bethesda, MD, USA CHANGES ------- * FIX: Multiecho fMRI crashing with 'unhashable type' errors by @oesteban in https://github.com/nipreps/mriqc/pull/1295 * FIX: Set ``n_procs`` instead of ``num_threads`` on node ``apply_hmc`` by @oesteban in https://github.com/nipreps/mriqc/pull/1309 * FIX: Address memory issues by limiting ``BigPlot``'s parallelization. by @oesteban in https://github.com/nipreps/mriqc/pull/1320 * FIX: Address memory issues in the DWI pipeline by @oesteban in https://github.com/nipreps/mriqc/pull/1323 * FIX: Limit IQMs' node number of processes and, therefore, memory by @oesteban in https://github.com/nipreps/mriqc/pull/1325 * FIX: Resolve numeric overflow in drift estimation node by @oesteban in https://github.com/nipreps/mriqc/pull/1324 * FIX: Revise bugfix #1324 by @oesteban in https://github.com/nipreps/mriqc/pull/1327 * FIX: Remove unreachable code within DWI pipeline by @oesteban in https://github.com/nipreps/mriqc/pull/1328 * ENH: Allow moving the cache folder with an environment variable by @oesteban in https://github.com/nipreps/mriqc/pull/1285 * ENH: Flatten multi-echo lists in circumstances that they fail by @oesteban in https://github.com/nipreps/mriqc/pull/1286 * ENH: Added type hints to config module by @zvi-quantivly in https://github.com/nipreps/mriqc/pull/1288 * ENH: Add test for the CLI parser by @jhlegarreta in https://github.com/nipreps/mriqc/pull/1293 * ENH: Add CLI entry point test by @jhlegarreta in https://github.com/nipreps/mriqc/pull/1294 * ENH: Add a development Dockerfile for testing local changes to the repo. by @rwblair in https://github.com/nipreps/mriqc/pull/1299 * ENH: Crawl dataset's metadata only once and before Nipype's workflow by @oesteban in https://github.com/nipreps/mriqc/pull/1317 * ENH(dMRI): Deal gracefully with small CC masks by @oesteban in https://github.com/nipreps/mriqc/pull/1311 * ENH: Leverage new spun-off apply interface by @oesteban in https://github.com/nipreps/mriqc/pull/1313 * MAINT: Removed personal information from maintainers and updated in contributors by @zvi-quantivly in https://github.com/nipreps/mriqc/pull/1289 * MAINT: Add JHLegarreta to the contributors list by @jhlegarreta in https://github.com/nipreps/mriqc/pull/1301 * MAINT: Flexibilize pandas pinned version by @oesteban in https://github.com/nipreps/mriqc/pull/1310 * MAINT: Remove *Pandas*'s ``FutureWarning`` by @oesteban in https://github.com/nipreps/mriqc/pull/1326 * DOC: Add description of ``summary_fg`` to the documentation by @celprov in https://github.com/nipreps/mriqc/pull/1306 * STY: Apply ruff/flake8-implicit-str-concat rule ISC001 by @DimitriPapadopoulos in https://github.com/nipreps/mriqc/pull/1296 * STY: Format *Jupyter notebooks* by @oesteban in https://github.com/nipreps/mriqc/pull/1321 **Full Changelog**: https://github.com/nipreps/mriqc/compare/24.0.0...24.0.1 24.0.0 (April 17, 2024) ======================= Initial major release of 2024, featuring the **extraction of IQMs from DWI data** for the first time in *MRIQC*'s timeline. CHANGES ------- * FIX: Bug in *toml* loader crashing with mixed arrays in config (#1281) * FIX: Remove *DataLad* as a node (#1278) * FIX: Calculation of trivial shells (#1276) * FIX: Finalized naming and connection of DWI IQMs (#1272) * FIX: Enable group reports for DWI (#1266) * FIX: Address issues that had broken the group reports (#1262) * FIX: Select filters if modalities are selected (#1261) * FIX: Make sure new logs and config file output are compatible with parallel processes (#1259) * FIX: Skip short BOLD runs that break outlier detection (#1120) * FIX: Revise config save/load and update inputs after dropping (#1245) * FIX: Drift should not be estimated when less than three low-b volumes present (#1242, #1243) * FIX: Handle ``NUMEXPR_MAX_THREADS`` like ``OMP_NUM_THREADS`` (#1241) * FIX: Exclude DWI runs with insufficient orientations or missing bvals (#1240) * FIX: Avert costly ``BIDSLayout.__repr__`` calls when saving config (#1239) * FIX: Duplicate node in anatomical workflow (#1234) * FIX: Typo in ``sorted(..., reverse=True)`` call (#1211) * ENH: Fail for non-formatted code (#1274) * ENH: Annotate nodes with ``n_procs`` to allow safe parallelization (#1277) * ENH: Mechanism to protect config's fields and write out config (#1258) * ENH: Improve documentation and logging of *SynthStrip*'s model (#1254) * ENH: Improve logging of runtime (#1253) * ENH: Expose a command-line option for minimum DWI volumes (#1249) * ENH: Improve error handling and logging (#1238) * ENH: Add *b*-vector angular deviations as IQMs (#1233) * ENH: Move from DTI to DKI with multishell data (#1230) * ENH: Noise floor estimated with PCA (``dwidenoise``) as an IQM (#1229) * ENH: Integrate PIESNO noise mask and sigma estimation (#1227) * ENH: Use MAD for robust estimation of sigma in the CC mask (#1228) * ENH: Add new IQM for DWI → NDC (#1226) * ENH: Add FA-based IQMs (nans percentage and degenerate percentage) (#1225) * ENH: Add computation of spiking voxels mask and percent IQMs (#1224) * ENH: Adds diffusion-related IQMs. (#1131) * ENH: Revise summary stats extraction and include controlled roundings (#1219) * DOC: Add changelog to documentation (#1217) * MAINT: Added ruff to development dependencies (#1271) * MAINT: Removed pre-commit from development dependencies (#1269) * MAINT: Clean up more ``FutureWarning`` issued by *Pandas* (#1257) * MAINT: Prevent pandas-originating deprecation warning (#1251) * MAINT: Move GitHub Actions and config files from *flake8* → *ruff* (#1212) * MAINT: Update contributor affiliation in ``CONTRIBUTORS.md`` (#1214) * STY: Reformat diffusion workflows module (#1279) * STY: Applied ruff formatting (#1273) 23.1.1 (March 20, 2024) ======================= A long-overdue hotfix release addressing many bugs generated during the development of the new dMRI workflows, and some relating to improvements of the handling of multi-echo fMRI. The release also include one year-worth of maintenance actions and a general code cleanup with *Ruff*. CHANGES ------- * FIX: Missing connection to head-motion estimation node in DWI workflow by `@oesteban `__ in `#1207 `__ * FIX: Revise porting to ``Loader`` by `@oesteban `__ in `#1201 `__ * FIX: Revise the last two sloppy merges by `@oesteban `__ in `#1200 `__ * FIX: Move from ``pkg_resources`` to ``niworkflows.data.Loader`` by `@oesteban `__ in `#1199 `__ * FIX: DIPY not listed as a dependency by `@oesteban `__ in `#1197 `__ * FIX: Include ``dwidenoise`` within Docker image by `@oesteban `__ in `#1196 `__ * FIX: Copy name attribute of ``dataset_description.json`` from input dataset by `@celprov `__ in `#1187 `__ * FIX: Remove FD as an ``iterfield`` in ``MapNode`` causing crash with ME-BOLD by `@celprov `__ in `#1179 `__ * FIX: Incorrect plugin metadata passed to *Report Assembler* by `@oesteban `__ in `#1188 `__ * FIX: Temporary fix of the missing ``"dwi"`` key by `@celprov `__ in `#1174 `__ * FIX: Rearrange multi-echo report by `@celprov `__ in `#1164 `__ * FIX: Typo in ``inputnode`` field in dMRI masking workflow by `@celprov `__ in `#1165 `__ * FIX: Bug in group level workflow by `@celprov `__ in `#1148 `__ * FIX: Bugs in DWI workflow by `@celprov `__ in `#1147 `__ * FIX: Use simpler DWI reference workflow by `@yibeichan `__ in `#1145 `__ * FIX: Drop deprecated *Networkx*'s API by `@celprov `__ in `#1137 `__ * FIX: Replace ``np.float`` by ``np.float64`` by `@celprov `__ in `#1140 `__ * ENH: Improved logging and optimize early checkpoint on subjects by `@oesteban `__ in `#1198 `__ * ENH: Store confound timeseries data by `@psadil `__ in `#1166 `__ * ENH: Large overhaul of the functional workflow w/focus on ME-EPI by `@oesteban `__ in `#1155 `__ * ENH: Implement BIDS filters file and drop legacy BIDS querying by `@oesteban `__ in `#1154 `__ * ENH: Swap background and zoomed-in visualizations in anatomical reports by `@oesteban `__ in `#1151 `__ * MAINT: Test on *Python* 3.12 by `@DimitriPapadopoulos `__ in `#1156 `__ * MAINT: Disable flaky T1w test on CircleCI by `@oesteban `__ in `#1202 `__ * MAINT: Overhaul of the ``Dockerfile`` by `@oesteban `__ in `#1195 `__ * MAINT: Revise package's extra dependencies by `@oesteban `__ in `#1194 `__ * MAINT: Clean up some ``setuptools_scm`` remnants by `@oesteban `__ in `#1193 `__ * MAINT: Load ``FMRISummary`` from *NiReports* rather than *NiWorkflows* by `@celprov `__ in `#1167 `__ * MAINT: Update to latest *migas*' API by `@mgxd `__ in `#1160 `__ * MAINT: Update bold to large resource class in ``config.yml`` by `@oesteban `__ in `#1158 `__ * MAINT: Refresh cached intermediate results by `@oesteban `__ in `#1143 `__ * MAINT: Simplify GitHub actions checks and update action versions by `@effigies `__ in `#1141 `__ * MAINT: Python 3.11 is supported by `@DimitriPapadopoulos `__ in `#1123 `__ * MAINT: Apply suggestions from pyupgrade by `@DimitriPapadopoulos `__ in `#1124 `__ * DOC: Update *Sphinx* pinned version to 5 by `@oesteban `__ in `#1192 `__ * DOC: http:// → https:// by `@DimitriPapadopoulos `__ in `#1126 `__ * DOC: Add info on the *FreeSurfer* requirement for bare install to address #1034 by `@neurorepro `__ in `#1130 `__ * STY: Add *Ruff* config and fix all warnings and errors by `@oesteban `__ in `#1203 `__ * STY: Remove extraneous parentheses by `@DimitriPapadopoulos `__ in `#1186 `__ * STY: Apply a few refurb suggestions by `@DimitriPapadopoulos `__ in `#1162 `__ * STY: Fix typo found by codespell by `@DimitriPapadopoulos `__ in `#1161 `__ 23.1.0 (June 14, 2023) ====================== A new minor release featuring the new individual reports built with the new *NiReports* VRS (visual reports system). This means *MRIQC* now uses the same package *fMRIPrep* uses for generating its reports. In addition to that, this new release also features *Beta* support for diffusion MRI (dMRI). CHANGES ------- * FIX: Better handling of BIDS cached indexation (`#1121 `__) * FIX: Make doctest of ``NumberOfShells`` more reliable (`#1122 `__) * FIX: Add protection for NaNs and INFs when calculating QI2 (`#1112 `__) * FIX: ``PlotMosaic`` expects lists, not tuples (`#1111 `__) * FIX: BIDS database directory handling (`#1110 `__) * FIX: Remove unused dipy import in the functional interfaces (`#1109 `__) * FIX: Refine the head mask after removal of FSL BET (`#1107 `__) * FIX: Inform *SynthStrip* about the desired intraop threads (`#1101 `__) * FIX: Test broken by #1098 (`#1100 `__) * FIX: Separate report bootstrap files (anat vs. func) (`#1098 `__) * FIX: Propagate logging level to subprocesses (`#1030 `__) * ENH: Incorporate new NiReports' DWI heatmaps (`#1119 `__) * ENH: More compact of shell-wise summary statistic maps (avg/std) (`#1116 `__) * ENH: Add a basic DTI fitting into the diffusion workflow (`#1115 `__) * ENH: MRIQC for DWI (`#1113 `__) * ENH: Culminate dropping FSL as a dependency (`#1108 `__) * ENH: Replace FSL FAST with ANTs Atropos for brain tissue segmentation (`#1099 `__) * ENH: Drop FSL MELODIC (without alternative) (`#1106 `__) * ENH: Drop FSL BET to estimate the "outskin" (head) mask (`#1105 `__) * ENH: Drop utilization of "head" mask from template (`#1104 `__) * ENH: Move templates' probsegs into individual at normalization (`#1103 `__) * ENH: Improving the resource monitor -- infer PID from process name (`#1049 `__) (`#1049 `__) * ENH: Refactor reports system to use *NiReports* and the general VRS (`#1085 `__) * MAINT: Move codespell configuration to ``pyproject.toml`` (`#1097 `__) * MAINT: Update deprecated ``nibabel.spatialimage.get_data()`` calls (`#1096 `__) .. admonition:: Author list for papers based on *MRIQC* 23.0 series As described in the `Contributor Guidelines `__, anyone listed as developer or contributor may write and submit manuscripts about *MRIQC*. To do so, please move the author(s) name(s) to the front of the following list: Zvi Baratz \ :sup:`1`\ ; Christopher J. Markiewicz \ :sup:`2`\ ; Eilidh MacNicol \ :sup:`3`\ ; Dylan Nielson \ :sup:`4`\ ; Jan Varada \ :sup:`5`\ ; Ross W. Blair \ :sup:`2`\ ; Céline Provins \ :sup:`6`\ ; William Triplett \ :sup:`7`\ ; Mathias Goncalves \ :sup:`2`\ ; Nikita Beliy \ :sup:`8`\ ; John A. Lee \ :sup:`9`\ ; Ursula A. Tooley \ :sup:`10`\ ; James D. Kent \ :sup:`11`\ ; Yaroslav O. Halchenko \ :sup:`12`\ ; Bennet Fauber \ :sup:`13`\ ; Taylor Salo \ :sup:`14`\ ; Michael Krause \ :sup:`15`\ ; Pablo Velasco \ :sup:`16`\ ; Thomas Nichols \ :sup:`17`\ ; Adam Huffman \ :sup:`18`\ ; Elodie Savary \ :sup:`6`\ ; Johannes Achtzehn \ :sup:`19`\ ; Joke Durnez \ :sup:`2`\ ; Satrajit S. Ghosh \ :sup:`20`\ ; Asier Erramuzpe \ :sup:`21`\ ; Benjamin Kay \ :sup:`22`\ ; Daniel Birman \ :sup:`2`\ ; McKenzie P. Hagen \ :sup:`23`\ ; Michael G. Clark \ :sup:`24`\ ; Patrick Sadil \ :sup:`25`\ ; Rafael Garcia-Dias \ :sup:`26`\ ; Adam G. Thomas \ :sup:`27`\ ; Russell A. Poldrack \ :sup:`2`\ ; Ariel Rokem \ :sup:`28`\ ; Oscar Esteban \ :sup:`6`\ . Affiliations: 1. Sagol School of Neuroscience, Tel Aviv University, Tel Aviv, Israel 2. Department of Psychology, Stanford University, CA, USA 3. Department of Neuroimaging, Institute of Psychiatry, Psychology and Neuroscience, King's College London, London, UK 4. Section on Clinical and Computational Psychiatry, National Institute of Mental Health, Bethesda, MD, USA 5. Functional MRI Facility, National Institute of Mental Health, Bethesda, MD, USA 6. Department of Radiology, Lausanne University Hospital and University of Lausanne, Switzerland 7. University of Florida: Gainesville, Florida, US 8. CRC ULiege, Liege, Belgium 9. Quansight, Dublin, Ireland 10. Department of Neuroscience, University of Pennsylvania, PA, USA 11. Department of Psychology, University of Texas at Austin, TX, USA 12. Psychological and Brain Sciences Department, Dartmouth College, NH, USA 13. University of Michigan, Ann Arbor, USA 14. Department of Psychology, Florida International University, FL, USA 15. Max Planck Institute for Human Development, Berlin, Germany 16. Center for Brain Imaging, New York University, NY, USA 17. Oxford Big Data Institute, University of Oxford, Oxford, GB 18. Department of Physics, Imperial College London, London, UK 19. Charité Berlin, Berlin, Germany 20. McGovern Institute for Brain Research, MIT, MA, USA; and Department of Otolaryngology, Harvard Medical School, MA, USA 21. Computational Neuroimaging Lab, BioCruces Health Research Institute 22. Washington University School of Medicine, St.Louis, MO, USA 23. Psychology Department, University of Washington, Seattle, WA, USA 24. National Institutes of Health, USA 25. Johns Hopkins Bloomberg School of Public Health, MD, USA 26. Institute of Psychiatry, Psychology & Neuroscience, King's College London, London, UK 27. Data Science and Sharing Team, National Institute of Mental Health, Bethesda, MD, USA 28. The University of Washington eScience Institute, WA, USA 23.0.1 (March 24, 2023) ======================= A hotfix release resolving a reggression introduced with the new optimized indexing. * FIX: Underspecified regex sets ``BIDSLayout`` to ignore data with sessions (`#1094 `__) * FIX: Input data has incompatible dimensionality (plotting ICA) (`#1082 `__) * ENH: Optimize metadata gathering reusing ``BIDSLayout`` db (`#1084 `__) * DOC : update anatomical example report in documentation (`#1088 `__) * MAINT: Drop old ``mriqc_plot`` script (`#1091 `__) 23.0.0 (March 10, 2023) ======================= The new 23.0.x series include several prominent changes. Visualization has been migrated from *MRIQC* and *niworkflows* over to the new *NiReports* project. This series include a major bugfix with **the optimization of the indexing** of the input BIDS folder, which was taking large times with sizeable datasets. Telemetry has also been incorporated with *migas*. These new series also involve maintenance housekeeping, and includes some relevant bugfixes. New contributors ---------------- * `@arokem `__ made their first contribution in `#1040 `__ * `@yarikoptic `__ made their first contribution in `#1057 `__ * `@esavary `__ made their first contribution in `#1047 `__ CHANGES ------- **Full Changelog**: https://github.com/nipreps/mriqc/compare/22.0.6...23.0.0 * FIX: Send metadata extraction to workers (functional workflow) (`#1081 `__) * FIX: Plot coronal as main plain for mosaic of rodent images (`#1027 `__) * FIX: Address non-empty take from empty axes (anatomical IQMs) (`#1077 `__) * FIX: Uniformize building workflow message (anat vs. func) (`#1072 `__) * FIX: Move telemetry atexit into entrypoint func (`#1067 `__) * FIX: Preempt PyBIDS to spend time indexing non-BIDS folders (`#1050 `__) * FIX: Update T1w metrics (`#1063 `__) * FIX: Resource monitor would not ever start tracking (`#1051 `__) * ENH: Add DataLad getter to inputs of functional workflows (`#1071 `__) * ENH: Add migas telemetry (`#1036 `__) * ENH: Add codespell automation: config, action, and typos fixed (`#1057 `__) * MAINT: Update *NiReports* calls to upcoming interfaces API (`#1078 `__) * MAINT: Pacify codespell (`#1080 `__) * MAINT: Conclude porting of reportlets into *NiReports* (`#1068 `__) * MAINT: Migrate to hatchling (`#1070 `__) * MAINT: Pin PyBIDS 0.15.6 (culminating #1050) (`#1069 `__) * MAINT: Update niworkflows pin to support newer ANTs releases (`#1047 `__) * MAINT: Fix minor aspects of WebAPI deployment on CircleCI (`#1064 `__) * MAINT: Update CircleCI executor and use built-in docker-compose (`#1061 `__) * MAINT: Rotate CircleCI secrets and setup up org-level context (`#1046 `__) * DOC: Update documentation with the new carpet plot (`#1045 `__) * DOC: Complete the documentation of ``summary_stats()`` (`#1044 `__) * DOC: Fixes a couple of broken links to the *nipype* documentation (`#1040 `__) .. admonition:: Author list for papers based on *MRIQC* 23.0 series As described in the `Contributor Guidelines `__, anyone listed as developer or contributor may write and submit manuscripts about *MRIQC*. To do so, please move the author(s) name(s) to the front of the following list: Zvi Baratz \ :sup:`1`\ ; Christopher J. Markiewicz \ :sup:`2`\ ; Eilidh MacNicol \ :sup:`3`\ ; Dylan Nielson \ :sup:`4`\ ; Jan Varada \ :sup:`5`\ ; Ross W. Blair \ :sup:`2`\ ; Céline Provins \ :sup:`6`\ ; William Triplett \ :sup:`7`\ ; Mathias Goncalves \ :sup:`2`\ ; Nikita Beliy \ :sup:`8`\ ; John A. Lee \ :sup:`9`\ ; Ursula A. Tooley \ :sup:`10`\ ; James D. Kent \ :sup:`11`\ ; Yaroslav O. Halchenko \ :sup:`12`\ ; Bennet Fauber \ :sup:`13`\ ; Taylor Salo \ :sup:`14`\ ; Michael Krause \ :sup:`15`\ ; Pablo Velasco \ :sup:`16`\ ; Thomas Nichols \ :sup:`17`\ ; Adam Huffman \ :sup:`18`\ ; Johannes Achtzehn \ :sup:`19`\ ; Joke Durnez \ :sup:`2`\ ; Satrajit S. Ghosh \ :sup:`20`\ ; Asier Erramuzpe \ :sup:`21`\ ; Benjamin Kay \ :sup:`22`\ ; Daniel Birman \ :sup:`2`\ ; Elodie Savary \ :sup:`23`\ ; McKenzie P. Hagen \ :sup:`24`\ ; Michael G. Clark \ :sup:`25`\ ; Patrick Sadil \ :sup:`26`\ ; Rafael Garcia-Dias \ :sup:`27`\ ; Adam G. Thomas \ :sup:`28`\ ; Russell A. Poldrack \ :sup:`2`\ ; Ariel Rokem \ :sup:`29`\ ; Oscar Esteban \ :sup:`30`\ . Affiliations: 1. Sagol School of Neuroscience, Tel Aviv University, Tel Aviv, Israel 2. Department of Psychology, Stanford University, CA, USA 3. Department of Neuroimaging, Institute of Psychiatry, Psychology and Neuroscience, King's College London, London, UK 4. Section on Clinical and Computational Psychiatry, National Institute of Mental Health, Bethesda, MD, USA 5. Functional MRI Facility, National Institute of Mental Health, Bethesda, MD, USA 6. Lausanne University Hospital and University of Lausanne, Lausanne, Switzerland 7. University of Florida: Gainesville, Florida, US 8. CRC ULiege, Liege, Belgium 9. Quansight, Dublin, Ireland 10. Department of Neuroscience, University of Pennsylvania, PA, USA 11. Department of Psychology, University of Texas at Austin, TX, USA 12. Psychological and Brain Sciences Department, Dartmouth College, NH, USA 13. University of Michigan, Ann Arbor, USA 14. Department of Psychology, Florida International University, FL, USA 15. Max Planck Institute for Human Development, Berlin, Germany 16. Center for Brain Imaging, New York University, NY, USA 17. Oxford Big Data Institute, University of Oxford, Oxford, GB 18. Department of Physics, Imperial College London, London, UK 19. Charité Berlin, Berlin, Germany 20. McGovern Institute for Brain Research, MIT, MA, USA; and Department of Otolaryngology, Harvard Medical School, MA, USA 21. Computational Neuroimaging Lab, BioCruces Health Research Institute 22. Washington University School of Medicine, St.Louis, MO, USA 23. Department of Radiology, Lausanne University Hospital and University of Lausanne, Switzerland 24. Psychology Department, University of Washington, Seattle, WA, USA 25. National Institutes of Health, USA 26. Johns Hopkins Bloomberg School of Public Health, MD, USA 27. Institute of Psychiatry, Psychology & Neuroscience, King's College London, London, UK 28. Data Science and Sharing Team, National Institute of Mental Health, Bethesda, MD, USA 29. The University of Washington eScience Institute, WA, USA 30. Department of Radiology, Lausanne University Hospital and University of Lausanne 22.0.6 (August 24, 2022) ======================== A hotfix release partially rolling-back the previous fix #1025. Thanks everyone for your patience with the excessively rushed release of 22.0.5. * FIX: Better fix to the multi-argument ``--participant-label`` issue (`#1026 `__) 22.0.5 (August 24, 2022) ======================== A hotfix release addressing a problem with the argument parser. * FIX: Multiple valued ``--participant-label`` wrongly parsed (`#1025 `__) 22.0.4 (August 23, 2022) ======================== A hotfix release to ensure smooth operation of datalad within Docker. * FIX: Major improvements to new datalad-based interface & perform within containers (`#1024 `__) * ENH: Bump Docker base to latest release (`#1022 `__) 22.0.3 (August 19, 2022) ======================== A patch release containing a bugfix to the SynthStrip preprocessing. * FIX: SynthStrip preprocessing miscalculating new shape after reorientation (`#1021 `__) * ENH: Remove slice-timing correction (`#1019 `__) * ENH: Add a new ``DataladIdentityInterface`` (`#1020 `__) * ENH: Set rat-specific defaults for FD calculations (`#1005 `__) * ENH: New version of the rating widget (`#1012 `__) * DOC: Move readthedocs to use the config v2 file (YAML) (`#1018 `__) * MAINT: Fix statsmodels dependency, it is not optional (`#1017 `__) * MAINT: Several critical updates to CircleCI and Docker images (`#1016 `__) * MAINT: Update the T1w IQMs to the new reference after #997 (`#1014 `__) * MAINT: Fix failing tests as ``python setup.py`` is deprecated (`#1013 `__) 22.0.2 (August 15, 2022) ======================== A patch release including the new ratings widget. * ENH: New version of the rating widget (`#1012 `__) * DOC: Move readthedocs to use the config v2 file (YAML) (`#1018 `__) * MAINT: Fix ``statsmodels`` dependency, it is not optional (`#1017 `__) * MAINT: Several critical updates to CircleCI and Docker images (`#1016 `__) * MAINT: Update the T1w IQMs to the new reference after #997 (`#1014 `__) * MAINT: Fix failing tests as ``python setup.py`` is deprecated (`#1013 `__) 22.0.1 (May 3rd, 2022) ====================== A patch release addressing a new minor bug. * FIX: More lenient handling of skull-stripped datasets (`#997 `__) 22.0.0 (May 3rd, 2022) ====================== First official release after migrating the repository into the *NiPreps*' organization. A major new feature is the rodent pipeline by Eilidh MacNicol (@eilidhmacnicol). A second major feature is the adoption of the updated carpet plots for BOLD fMRI, contributed by Céline Provins (@celprov). Virtual memory allocation has been ten-fold cut down, and a complementary resource monitor instrumentation is now available with *MRIQC*. This release updates the Docker image with up-to-date dependencies, updates *MRIQC*'s codebase to the latest *NiTransforms* and includes some minor bugfixes. The code, modules and data related to the MRIQC classifier have been extracted into an isolated package called [*MRIQC-learn*](https://github.com/nipreps/mriqc-learn). Finally, this release also contains a major code style overhaul by Zvi Baratz. The contributor/author crediting system has been adapted to the current draft of the *NiPreps Community* Governance documents. With thanks to @ZviBaratz, @nbeliy, @octomike, @benkay86, @verdurin, @leej3, @utooley, and @jAchtzehn for their contributions. * FIX: Inconsistent API in anatomical CNR computation (`#995 `__) * FIX: Check sanity of input data before extracting IQMs (`#994 `__) * FIX: Plot segmentations after dropping off-diagonal (`#989 `__) * FIX: Replace all deprecated ``nibabel.get_data()`` in anatomical module (`#988 `__) * FIX: Resource profiler was broken with config file (`#981 `__) * FIX: preserve WM segments in rodents (`#979 `__) * FIX: Pin ``jinja2 < 3.1`` (`#978 `__) * FIX: Make toml config unique, works around #912 (`#960 `__) * FIX: Nipype multiproc plugin expects ``n_procs`` and not ``nprocs`` (`#961 `__) * FIX: Set TR when generating carpetplots (enables time for X axis) (`#971 `__) * FIX: ``template_resolution`` deprecation warning (`#941 `__) * FIX: Set entity ``datatype`` in ``BIDSLayout`` queries (`#942 `__) * FIX: T2w image of MNI template unavailable in Singularity (`#940 `__) * FIX: Release process -- Docker deployment not working + Python package lacks WebAPI token (`#938 `__) * FIX: Revise building documentation at RTD after migration (`#935 `__) * FIX: Final touch-ups in the maintenance of Docker image + CI (`#928 `__) * FIX: Update unit tests (`#927 `__) * FIX: Update dependencies and repair BOLD workflow accordingly (`#926 `__) * FIX: Update dependencies and repair T1w workflow accordingly (`#925 `__) * FIX: Set ``matplotlib`` on ``Agg`` output mode (`#892 `__) * ENH: Deprecate ``--start-idx`` / ``--stop-idx`` (`#993 `__) * ENH: Add SynthStrip base module (`#987 `__) * ENH: Improve building workflow message feedback (`#990 `__) * ENH: Add instrumentation to monitor resources (`#984 `__) * ENH: Standalone, lightweight version of MultiProc plugin (`#985 `__) * ENH: Revise plugin and workflow initialization (`#983 `__) * ENH: Base generalization of the pipeline for rodents (`#969 `__) * ENH: Update to new *NiWorkflows*' API, which adds the crown to the carpetplot (`#968 `__) * ENH: Optimize *PyBIDS*' layout initialization (`#939 `__) * ENH: Refactored long strings to a :mod:`mriqc.messages` module (`#901 `__) * ENH: Refactored :mod:`mriqc.interfaces.common` module (`#901 `__) * DOC: Improve documentation of ``--nprocs`` and ``--omp-nthreads`` (`#986 `__) * DOC: Add ``sbatch`` file example for SLURM execution (`#963 `__) * DOC: Various fixes to "Running mriqc" section (`#897 `__) * MAINT: Refactor ``Dockerfile`` using new miniconda image (`#974 `__) * MAINT: Outsource the classifier into nipreps/mriqc-learn (`#973 `__) * MAINT: Update ``CONTRIBUTORS.md`` (`#953 `__) * MAINT: Update contributor location (`#952 `__) * MAINT: Updates to ``CONTRIBUTORS.md`` file * MAINT: Revise Docker image settings & CircleCI (`#937 `__) * MAINT: Finalize transfer to ``nipreps`` organization (`#936 `__) * MAINT: Relicensing to Apache-2.0, for compliance with *NiPreps* and prior transfer to the org (`#930 `__) * MAINT: New Docker layer caching system of other *NiPreps* (`#929 `__) * MAINT: Code style overhaul (`#901 `__) * MAINT: Update ``Dockerfile`` and catch-up with *fMRIPrep*'s (`#924 `__) * STY: Run ``black`` at the top of the repo (`#932 `__) **Full Changelog**: https://github.com/nipreps/mriqc/compare/0.16.1...22.0.0 .. admonition:: Author list for papers based on *MRIQC* 22.0.x As described in the `Contributor Guidelines `__, anyone listed as developer or contributor may write and submit manuscripts about *MRIQC*. To do so, please move the author(s) name(s) to the front of the following list: Zvi Baratz \ :sup:`1`\ ; Christopher J. Markiewicz \ :sup:`2`\ ; Eilidh MacNicol \ :sup:`3`\ ; Dylan Nielson \ :sup:`4`\ ; Jan Varada \ :sup:`5`\ ; Ross W. Blair \ :sup:`2`\ ; William Triplett \ :sup:`6`\ ; Nikita Beliy \ :sup:`7`\ ; Céline Provins \ :sup:`8`\ ; John A. Lee \ :sup:`9`\ ; Ursula A. Tooley \ :sup:`10`\ ; James D. Kent \ :sup:`11`\ ; Bennet Fauber \ :sup:`12`\ ; Taylor Salo \ :sup:`13`\ ; Mathias Goncalves \ :sup:`2`\ ; Michael Krause \ :sup:`14`\ ; Pablo Velasco \ :sup:`15`\ ; Thomas Nichols \ :sup:`16`\ ; Adam Huffman \ :sup:`17`\ ; Johannes Achtzehn \ :sup:`18`\ ; Joke Durnez \ :sup:`2`\ ; Satrajit S. Ghosh \ :sup:`19`\ ; Asier Erramuzpe \ :sup:`20`\ ; Benjamin Kay \ :sup:`21`\ ; Daniel Birman \ :sup:`2`\ ; Michael G. Clark \ :sup:`22`\ ; Rafael Garcia-Dias \ :sup:`23`\ ; Sean Marret \ :sup:`5`\ ; Adam G. Thomas \ :sup:`24`\ ; Russell A. Poldrack \ :sup:`2`\ ; Krzysztof J. Gorgolewski \ :sup:`25`\ ; Oscar Esteban \ :sup:`26`\ . Affiliations: 1. Sagol School of Neuroscience, Tel-Aviv University 2. Department of Psychology, Stanford University, CA, USA 3. Department of Neuroimaging, Institute of Psychiatry, Psychology and Neuroscience, King's College London, London, UK 4. Section on Clinical and Computational Psychiatry, National Institute of Mental Health, Bethesda, MD, USA 5. Functional MRI Facility, National Institute of Mental Health, Bethesda, MD, USA 6. University of Florida: Gainesville, Florida, US 7. CRC ULiege, Liege, Belgium 8. Lausanne University Hospital and University of Lausanne, Lausanne, Switzerland 9. Quansight, Dublin, Ireland 10. Department of Neuroscience, University of Pennsylvania, PA, USA 11. Department of Psychology, University of Texas at Austin, TX, USA 12. University of Michigan, Ann Arbor, USA 13. Department of Psychology, Florida International University, FL, USA 14. Max Planck Institute for Human Development, Berlin, Germany 15. Center for Brain Imaging, New York University, NY, USA 16. Oxford Big Data Institute, University of Oxford, Oxford, GB 17. Department of Physics, Imperial College London, London, UK 18. Charité Berlin, Berlin, Germany 19. McGovern Institute for Brain Research, MIT, MA, USA; and Department of Otolaryngology, Harvard Medical School, MA, USA 20. Computational Neuroimaging Lab, BioCruces Health Research Institute 21. Washington University School of Medicine, St.Louis, MO, USA 22. National Institutes of Health, USA 23. Institute of Psychiatry, Psychology & Neuroscience, King's College London, London, UK 24. Data Science and Sharing Team, National Institute of Mental Health, Bethesda, MD, USA 25. Google LLC 26. Department of Radiology, Lausanne University Hospital and University of Lausanne Series 0.16.x ============= 0.16.1 (January 30, 2021) ------------------------- Bug-fix release in 0.16.x series. This PR improves BIDS Derivatives compliance, fixes an issue with reading datasets with subjects of the form ``sub-sXYZ``, and improves compatibility with more recent matplotlib. * FIX: Participant labels starting with ``[sub]`` cannot be used (`#890 `__) * FIX: Change deprecated ``normed`` to ``density`` in parameters to ``hist()`` (`#888 `__) * ENH: Write derivatives metadata (`#885 `__) * ENH: Add ``--pdb`` option to make debugging easier (`#884 `__) 0.16.0 (January 5, 2021) ------------------------ New feature release in 0.16.x series. This version removes the FSL dependency from the fMRI workflow. * FIX: Skip version cache on read-only filesystems (`#862 `__) * FIX: Honor ``$OMP_NUM_THREADS`` environment variable (`#848 `__) * RF: Simplify comprehensions, using easy-to-read var names (`#875 `__) * RF: Free the fMRI workflow from FSL (`#842 `__) * CI: Fix up Circle builds (`#876 `__) * CI: Update machine images on Circle (`#874 `__) Older (unsupported) series ========================== 0.15.3 (September 18, 2020) --------------------------- A bugfix release to re-enable setting of ``--omp-nthreads/--ants-nthreads``. * FIX: ``omp_nthreads`` typo (`#846 `__) 0.15.2 (April 6, 2020) ---------------------- A bugfix release containing mostly maintenance actions and documentation improvements. This version drops Python 3.5. The core of MRIQC has adopted the config-module pattern from fMRIPrep. With thanks to A. Erramuzpe, @justbennet, U. Tooley, and A. Huffman for contributions. * MAINT: revise style of all files (except for workflows) (`#839 `__) * MAINT: Clear the clutter of warnings (`#838 `__) * RF: Adopt config module pattern from *fMRIPrep* (`#837 `__) * MAINT: Clear the clutter of warnings (`#838 `__) * MAINT: Drop Python 3.5, simplify linting (`#833 `__) * MAINT: Update to latest Ubuntu Xenial tag (`#814 `__) * MAINT: Centralize all requirements and versions on ``setup.cfg`` (`#819 `__) * MAINT: Use recent Python image to build packages in CircleCI (`#808 `__) * DOC: Improve AQI (and other IQMs) and boxplot whiskers descriptions (`#816 `__) * DOC: Refactor how documentation is built on CircleCI (`#818 `__) * DOC: Corrected a couple of typos in ``--help`` text (`#809 `__) 0.15.1 (July 26, 2019) ---------------------- A maintenance patch release updating PyBIDS. * FIX: ``FileNotFoundError`` when MELODIC (``--ica``) does not converge (`#800 `__) @oesteban * MAINT: Migrate MRIQC to a ``setup.cfg`` style of installation (`#799 `__) @oesteban * MAINT: Use PyBIDS 0.9.2+ via niworkflows PR (`#796 `__) @effigies 0.15.0 (April 5, 2019) ---------------------- A long overdue update, pinning updated versions of `TemplateFlow `__ and `Niworkflows `__. With thanks to @garciadias for contributions. * ENH: Revision of QI2 (`#606 `__) @oesteban * FIX: Set matplotlib backend early (`#759 `__) @oesteban * FIX: Niworkflows pin <0.5 (`#766 `__) @oesteban * DOC: Update BIDS validation link. (`#764 `__) @garciadias * DOC: Add data sharing agreement (`#765 `__) @oesteban * FIX: Catch uncaught exception in WebAPI upload. (`#774 `__) @rwblair * FIX/DOC: Append new line after dashes in ``mriqc_run`` help text (`#777 `__) @rwblair * ENH: Use TemplateFlow and niworkflows-0.8.x (`#782 `__) @oesteban * FIX: Correctly set WebAPI rating endpoint in BOLD reports. (`#785 `__) @oesteban * FIX: Correctly process values of rating widget (`#787 `__) @oesteban 0.14.2 (August 20, 2018) ------------------------ * FIX: Preempt pandas resolving ``Path`` objects (`#746 `__) @oesteban * FIX: Codacy issues (`#745 `__) @oesteban 0.14.1 (August 20, 2018) ------------------------ * FIX: Calculate relative path with sessions (`#742 `__) @oesteban * ENH: Add a toggle button to rating widget (`#743 `__) @oesteban 0.14.0 (August 17, 2018) ------------------------ * ENH: New feedback widget (`#740 `__) @oesteban 0.13.1 (August 16, 2018) ------------------------ * [ENH,FIX] Updates to individual reports, fix table after rating (`#739 `__) @oesteban 0.13.0 (August 15, 2018) ------------------------ * MAINT: Overdue refactor (`#736 `__) @oesteban * FIX: Reorganize outputs (closes #396) * ENH: Memory usage - lessons learned with FMRIPREP (`#703 `__) * FIX: Cannot allocate memory (v 0.9.4) (closes #536) * FIX: Drop inoperative ``--report-dir`` flag (`#550 `__) * FIX: Drop misleading WARNING of the group-level execution (`#714 `__) * FIX: Expand usernames on input paths (`#721 `__) * MAINT: More robust naming of derivatives (related to #661) * FIX: Do not fail with spurious 4th dimension on T1w (`#738 `__) @oesteban * ENH: Move on to .tsv files (`#737 `__) @oesteban 0.12.1 (August 13, 2018) ------------------------ * FIX: ``BIDSLayout`` queries (`#735 `__) 0.12.0 (August 09, 2018) ------------------------ * FIX: Reduce tSNR memory requirements (`#712 `__) * DOC: Fix typos in IQM documentation (`#725 `__) * PIN: Update MRIQC WebAPI version (`#734 `__) * BUG: Fix missing library in singularity images (`#733 `__) * PIN: nipype 1.1.0, niworkflows (`#726 `__) 0.11.0 (June 05, 2018) ---------------------- * RF: Resume external nipype dependency (`#715 `__) 0.10.6 (May 29, 2018) --------------------- * HOTFIX: Bug #659 0.10.5 (May 28, 2018) --------------------- * ENH: Report feedback (`#659 `__) 0.10.4 (March 22, 2018) ----------------------- * ENH: Various improvements to reports (`#708 `__) * MAINT: Style revision (`#704 `__) * PIN: pybids 0.5 (`#700 `__) * ENH: Increase FAST memory limits (`#702 `__) 0.10.3 (February 26, 2018) -------------------------- * ENH: Enable T2w metrics uploads (`#696 `__) * PIN: Updating niworkflows (`#698 `__) * DOC: Option ``-o`` is outdated for classifier (`#697 `__) 0.10.2 (February 15, 2018) -------------------------- * ENH: Add warning about mounting relative paths (`#690 `__) * FIX: Sanitize inputs (`#687 `__) * DOC: Fix documentation to use ``--version`` instead of ``-v`` (`#688 `__) 0.10.1 ------ * FIX: Fixed a bug in reading outputs of ``3dFWHMx`` (`#678 `__) 0.9.10 ------ * FIX: Updated AFNI to 17.3.03. Resolves errors regarding opening display by ``3dSkullStrip`` (`#669 `__) 0.9.9 ----- * ENH: Update nipype to fix ``$DISPLAY`` problem of AFNI's ``3dSkullStrip`` 0.9.8 ----- With thanks to Jan Varada (@jvarada) for the session/run filtering. * ENH: Report recall in cross-validation (requested by reviewer) (`#633 `__) * ENH: Hotfixes to 0.9.7 (`#635 `__) * FIX: Implement filters for session, run and task of BIDS input (`#612 `__) 0.9.7 ----- * ENH: Clip outliers in FD and SPIKES group plots (`#593 `__) * ENH: Second revision of the classifier (`#555 `__): * Set matplotlib plugin to `agg` in docker image * Migrate scalings to sklearn pipelining system * Add Satra's feature selection for RFC (with thanks to S. Ghosh for his suggestion) * Make model selection compatible with sklearn `Pipeline` * Multiclass classification * Add feature selection filter based on Sites prediction (requires pinning to development sklearn-0.19) * Add `RobustLeavePGroupsOut`, replace `RobustGridSearchCV` with the standard `GridSearchCV` of sklearn. * Choice between `RepeatedStratifiedKFold` and `RobustLeavePGroupsOut` in `mriqc_clf` * Write cross-validation results to an `.npz` file. * ENH: First revision of the classifier (`#553 `__): * Add the possibility of changing the scorer function. * Unifize labels for raters in data tables (to `rater_1`) * Add the possibility of setting a custom decision threshold * Write the probabilities in the prediction file * Revised `mriqc_clf` processing flow * Revised labels file for ds030. * Add IQMs for ABIDE and DS030 calculated with MRIQC 0.9.6. * ANNOUNCEMENT: Dropped support for Python<-3.4 * WARNING (`#596 `__): We have changed the default number of threads for ANTs. Using parallelism with ANTs causes numerical instability on the calculated measures. The most sensitive metrics to this problem are the kurtosis calculations on the intensities of regions and qi_2. 0.9.6 ----- * ENH: Finished setting up `MRIQC Web API `_ * ENH: Better error message when --participant_label is set (`#542 `__) * FIX: Allow --load-classifier option to be empty in mriqc_clf (`#544 `__) * FIX: Borked bias estimation derived from Conform (`#541 `__) * ENH: Test against web API 0.3.2 (`#540 `__) * ENH: Change the default Web API address (`#539 `__) * ENH: MRIQCWebAPI: hash fields that may have PI (`#538 `__) * ENH: Added token authorization to MRIQCWebAPI client (`#535 `__) * FIX: Do not mask and antsAffineInitializer twice (`#534 `__) * FIX: Datasets where air (hat) mask is empty (`#533 `__) * ENH: Integration testing for MRIQCWebAPI (`#520 `__) * ENH: Use AFNI to calculate gcor (`#531 `__) * ENH: Refactor derivatives (`#530 `__) * ENH: New bold-IQM: dummy_trs (non-stady state volumes) (`#524 `__) * FIX: Order of BIDS components in IQMs CSV table (`#525 `__) * ENH: Improved logging of mriqc_run (`#526 `__) 0.9.5 ----- * ENH: Refactored structural metrics calculation (`#513 `__) * ENH: Calculate rotation mask (`#515 `__) * ENH: Intensity harmonization in the anatomical workflow (`#510 `__) * ENH: Set N4BiasFieldCorrection number of threads (`#506 `__) * ENH: Convert FWHM in pixel units (`#503 `__) * ENH: Add MRIQC client for feature crowdsourcing (`#464 `__) * DOC: Fix functional feature labels in documentation (docs_only) (`#507 `__) * FIX: New implementation for the rPVE feature (normalization, left-tail values) (`#505 `__) * ENH: Parse BIDS selectors (run, task, etc.), improve CLI (`#504 `__) 0.9.4 ----- * ANNOUNCEMENT: Dropped Python 2 support * ENH: Use versioneer to handle versions (`#500 `__) * ENH: Speed up spatial normalization (`#495 `__) * ENH: Resampling of hat mask and TPMs with linear interp (`#498 `__) * TST: Build documentation in CircleCI (`#484 `__) * ENH: Use full-resolution T1w images from ABIDE (`#486 `__) * TST: Parallelize tests (`#493 `__) * TST: Binding /etc/localtime stopped working in docker 1.9.1 (`#492 `__) * TST: Downgrade docker to 1.9.1 in circle (build_only) (`#491 `__) * TST: Check for changes in intermediate nifti files (`#485 `__) * FIX: Erroneous flag --n_proc in CircleCI (`#490 `__) * ENH: Add build_only tag to circle builds (`#488 `__) * ENH: Update Dockerfile (`#482 `__) * FIX: Ignore --profile flag with Linear plugin (`#483 `__) * DOC: Deep revision of the documentation (`#479 `__) * ENH: Minor improvements: SpatialNormalization and segmentation (`#472 `__) * ENH: Fixed typo for neurodebian install via apt-get (`#478 `__) * ENH: Updating fs2gif script (`#465 `__) * ENH: RF: Use niworkflows.interface.SimpleInterface (`#468 `__) * ENH: Add reproducibility of metrics tracking (`#466 `__) Release 0.9.3 ------------- * ENH: Reafactor of the Dockerfile to improve transparency, reduce size, and enable injecting code in Singularity (`#457 `__) * ENH: Make more the memory consumption estimates of each processing step more conservative to improve robustness (`#456 `__) * FIX: Minor documentation cleanups (`#461 `__) Release 0.9.2 ------------- * ENH: Optional ICA reports for identifying spatiotemporal artifacts (`#412 `__) * ENH: Add --profile flag (`#435 `__) * ENH: Crashfiles are saved in plain text to improve portability (`#434 `__) * FIX: Fixes EPI mask erosion (`#442 `__) * ENH: Make FSL and AFNI motion correction more comparable by using the same scheme for defining the reference image (`#444 `__) * FIX: Temporarily disabling T1w quality classifier until it can be retrained on new measures (`#447 `__) Release 0.9.1 ------------- * ENH: Add mriqc version and input image hash to IQMs json file (`#432 `__) * FIX: Affine and warp transforms are now applied in the correct order (`#431 `__) Release 0.9.0-2 --------------- * ENH: Revise Docker paths (`#429 `__) * FIX: Greedy participant selection (`#426 `__) * FIX: Pin pybids to new version 0.1.0 (`#427 `__) * FIX: Amends sloppy PR #425 (`#428 `__) Release 0.9.0-1 --------------- * FIX: BOLD reports clipped IQMs after spikes_num (`#425 `__) * FIX: Unicode error writing group reports (`#424 `__) * FIX: Respect Nifi header in fMRI conform node (`#415 `__) * DOC: Deep revision of documentation (#411, #416) * ENH: Added sphinx extension to plot workflow graphs (`#411 `__) * FIX: Removed repeated bias correction on anatomical workflows (`#410 `__) * FIX: Race condition in bold workflow when using shared workdir (`#409 `__) * FIX: Tests (#408, #407, #405) * FIX: Remove CDN for group level reports (`#406 `__) * FIX: Unused connection, matplotlib segfault (#403, #402) * ENH: Skip SpikeFFT detector by default (`#400 `__) * ENH: Use float32 (`#399 `__) * ENH: Spike finder performance improvoments (`#398 `__) * ENH: Basic T2w workflow (`#394 `__) * ENH: Re-enable 3dvolreg (`#390 `__) * ENH: Add T1w classifier (`#389 `__) Release 0.9.0-0 --------------- * FIX: Remove non-repeatable step from pipeline (`#369 `__) * ENH: Improve group level command line, with more informative output when no IQMs are found for a modality (`#372 `__) * ENH: Make group reports self-contained (`#333 `__) * FIX: New mosaics, based on old ones (#361, #360, #334) * FIX: Require numpy>=1.12 to avoid casting problems (`#356 `__) * FIX: Add support for acq and rec tags of BIDS (`#346 `__) * DOC: Documentation updates (`#350 `__) * FIX: pybids compatibility "No scans were found" (#340, #347, #342) * ENH: Rewrite PYTHONPATH in docker/singularity images (`#345 `__) * ENH: Move metadata onto the bottom of the individual reports (`#332 `__) * ENH: Don't include MNI registration report unlesS --verbose-reports is used (`#362 `__) Release 0.8.9 ------------- * ENH: Added registration svg panel to reports (`#297 `__) Release 0.8.8 ------------- * FIX: Bug translating int16 to uint8 in conform image. * FIX: Error in ConformImage interface (`#297 `__) * ENH: Replace BBR by ANTs (#295, #296) * FIX: Singularity: user-environment leaking into container (`#293 `__) * ENH: Report failed cases in group report (`#291 `__) * FIX: Brighter anatomical --verbose-reports (`#290 `__) * FIX: X-flip in the mosaics (`#289 `__) * ENH: Show metadata in the individual report (`#288 `__) * ENH: Label in the cutoff threshold - fmriplot (`#287 `__) * ENH: PyBIDS (`#286 `__) * ENH: Simplify tests (`#284 `__) * FIX: MRIQC crashed generating csv files (`#283 `__) * FIX: Bug in setup.py (`#281 `__) * ENH: Makefile (`#280 `__) * FIX: Revision of IQMs (#266, #272, #279) * ENH: Deprecation of --nthreads, new flags (`#260 `__) * ENH: Improvements on plots rendering (#254, #257, #258, #267, #268, #269, #270) * ENH: FFT detection of spikes (#253, #272) * FIX: Labels and links of samples in group plots (`#249 `__) * ENH: Units in group plots (`#242 `__) * FIX: More reliable group level (`#238 `__) * ENH: Add --verbose-reports for fMRI (`#236 `__) * ENH: Migrate functional reports to html (`#232 `__) * ENH: Add 0.2 FD cutoff line (`#231 `__) * ENH: Add AFNI's outlier count to carpet plot confound charts (`#230 `__) Release 0.8.7 ------------- * ENH: Anatomical Group reports in html (`#227 `__) * ENH: Add kurtosis to summary statistics (`#224 `__) * ENH: New report layout for fMRI, added carpetplot (`#198 `__) * ENH: Anatomical workflow refactor (`#219 `__). Release 0.8.6 ------------- * [FIX, CRITICAL] Do not chmod in Docker internal scripts * FIX: Error creating derivatives folder * ENH: Moved MNI spatial normalization to NIworkflows, and made robust. * ENH: De-coupled participant and group (reports) levels * ENH: Use new FD and DVARs calculations from nipype (`#172 `__) * ENH: Started with python3 compatibility * ENH: Added new M2WM measure #158 * FIX: QI2 is skipped if background intensity is not appropriate (`#147 `__) Release 0.8.5 ------------- * FIX: Error inverting the T1w-to-MNI warping (`#146 `__) * FIX: TypeError computing DVARS (`#145 `__) * ENH: Plot figure of fitted background chi for QI2 (`#143 `__) * ENH: Move skull-stripping and reorient to NIworkflows (`#142 `__) * FIX: mriqc crashes if no anatomical scans are found (`#141 `__) * DOC: Added acknowledgments to CPAC team members (`#134 `__) * ENH: Use absolute imports (`#133 `__) * FIX: VisibleDeprecationWarning (`#132 `__) * ENH: Provide full FD/DVARS files (`#128 `__) * ENH: Use MCFLIRT to compute motion parameters. AFNI's 3dvolreg now is optional (`#121 `__) * FIX: BIDS trees with anatomical images with different acquisition tokens (`#116 `__) * FIX: BIDS trees with anatomical images with several runs (`#112 `__) * ENH: Options for ANTs normalization: reduced test times (`#124 `__), and updated options (`#115 `__) Release 0.8.4 ------------- * ENH: PDF reports now use RST templates and jinja2 (`#109 `__) * FIX: Single-session-multiple-run anatomical files were not correctly located (`#112 `__) Release 0.8.3 ------------- * DOC: Added examples of the PDF reports (`#107 `__) * FIX: Fixed problems with Python 3 when generating reports. Release 0.8.2 ------------- * ENH: Python 3 compatibility (`#99 `__) * ENH: Add JSON settings file for ANTS (`#95 `__) * ENH: Generate reports automatically if mriqc is run without the -S flag (`#93 `__) * FIX: Revised implementation of QI2 measure (`#90 `__) * AGAVE: Fixed docker image for agave (`#89 `__) * FIX: Problem when generating the air mask with dipy installed (`#88 `__) * ENH: One-session-one-run execution mode (`#85 `__) * AGAVE: Added an agave app description generator (`#84 `__) Release 0.3.0 ------------- * ENH: Updated CircleCI and Docker to use the version 2.1.0 of ANTs compiled by their developers. * ENH: New anatomical workflows to compute the air mask (`#56 `__) Release 0.1.0 ------------- * FIX: #55 * ENH: Added rotation of output csv files if they exist Release 0.0.2 ------------- * ENH: Completed migration from QAP * ENH: Integration with ReadTheDocs * ENH: Submission to PyPi Release 0.0.1 ------------- * Basic mriqc functionality ================================================ FILE: Dockerfile ================================================ # MRIQC Docker Container Image distribution # # MIT License # # Copyright (c) 2021 The NiPreps Developers # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. # Ubuntu 22.04 LTS - Jammy ARG BASE_IMAGE=ubuntu:jammy-20240125 # # Build wheel # FROM python:slim AS src RUN apt-get update && \ DEBIAN_FRONTEND=noninteractive \ apt-get install -y --no-install-recommends git ARG VERSION ENV SETUPTOOLS_SCM_PRETEND_VERSION=$VERSION RUN python -m pip install -U pip build COPY . /src RUN python -m build /src # Utilities for downloading packages FROM ${BASE_IMAGE} as downloader # Bump the date to current to refresh curl/certificates/etc RUN echo "2024.03.18" RUN apt-get update && \ DEBIAN_FRONTEND=noninteractive \ apt-get install -y --no-install-recommends \ binutils \ bzip2 \ ca-certificates \ curl \ unzip && \ apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* # AFNI FROM downloader as afni # Bump the date to current to update AFNI RUN echo "2024.03.18" RUN mkdir -p /opt/afni-latest \ && curl -fsSL --retry 5 https://afni.nimh.nih.gov/pub/dist/tgz/linux_openmp_64.tgz \ | tar -xz -C /opt/afni-latest --strip-components 1 \ --exclude "linux_openmp_64/*.gz" \ --exclude "linux_openmp_64/funstuff" \ --exclude "linux_openmp_64/shiny" \ --exclude "linux_openmp_64/afnipy" \ --exclude "linux_openmp_64/lib/RetroTS" \ --exclude "linux_openmp_64/lib_RetroTS" \ --exclude "linux_openmp_64/meica.libs" \ # Keep only what we use && find /opt/afni-latest -type f -not \( \ -name "3dAutomask" \ -or -name "3dcalc" \ -or -name "3dFWHMx" \ -or -name "3dinfo" \ -or -name "3dmaskave" \ -or -name "3dSkullStrip" \ -or -name "3dTnorm" \ -or -name "3dToutcount" \ -or -name "3dTqual" \ -or -name "3dTshift" \ -or -name "3dTstat" \ -or -name "3dUnifize" \ -or -name "3dvolreg" \ -or -name "@compute_gcor" \ -or -name "afni" \ \) -delete # Use Ubuntu 20.04 LTS FROM nipreps/miniconda:py39_2403.0 ARG DEBIAN_FRONTEND=noninteractive ENV LD_LIBRARY_PATH="/usr/lib/x86_64-linux-gnu:${CONDA_PATH}/lib" ENV CONDA_PATH="/opt/conda" # Configure PPAs for libpng12 and libxp6 RUN GNUPGHOME=/tmp gpg --keyserver hkps://keyserver.ubuntu.com --no-default-keyring --keyring /usr/share/keyrings/linuxuprising.gpg --recv 0xEA8CACC073C3DB2A \ && GNUPGHOME=/tmp gpg --keyserver hkps://keyserver.ubuntu.com --no-default-keyring --keyring /usr/share/keyrings/zeehio.gpg --recv 0xA1301338A3A48C4A \ && echo "deb [signed-by=/usr/share/keyrings/linuxuprising.gpg] https://ppa.launchpadcontent.net/linuxuprising/libpng12/ubuntu jammy main" > /etc/apt/sources.list.d/linuxuprising.list \ && echo "deb [signed-by=/usr/share/keyrings/zeehio.gpg] https://ppa.launchpadcontent.net/zeehio/libxp/ubuntu jammy main" > /etc/apt/sources.list.d/zeehio.list # Dependencies for AFNI; requires a discontinued multiarch-support package from bionic (18.04) RUN apt-get update -qq \ && apt-get install -y -q --no-install-recommends \ ed \ gsl-bin \ libglib2.0-0 \ libglu1-mesa-dev \ libglw1-mesa \ libgomp1 \ libjpeg62 \ libpng12-0 \ libxm4 \ libxp6 \ netpbm \ tcsh \ xfonts-base \ xvfb \ && curl -sSL --retry 5 -o /tmp/multiarch.deb http://archive.ubuntu.com/ubuntu/pool/main/g/glibc/multiarch-support_2.27-3ubuntu1.5_amd64.deb \ && dpkg -i /tmp/multiarch.deb \ && rm /tmp/multiarch.deb \ && apt-get install -f \ && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \ && gsl2_path="$(find / -name 'libgsl.so.19' || printf '')" \ && if [ -n "$gsl2_path" ]; then \ ln -sfv "$gsl2_path" "$(dirname $gsl2_path)/libgsl.so.0"; \ fi \ && ldconfig # Install AFNI ENV AFNI_DIR="/opt/afni" COPY --from=afni /opt/afni-latest ${AFNI_DIR} ENV PATH="${AFNI_DIR}:$PATH" \ AFNI_IMSAVE_WARNINGS="NO" \ AFNI_MODELPATH="${AFNI_DIR}/models" \ AFNI_TTATLAS_DATASET="${AFNI_DIR}/atlases" \ AFNI_PLUGINPATH="${AFNI_DIR}/plugins" # Install AFNI's dependencies RUN micromamba install -n base -c conda-forge "ants=2.5" \ && sync \ && micromamba clean -afy; sync \ && ldconfig # Unless otherwise specified each process should only use one thread - nipype # will handle parallelization ENV MKL_NUM_THREADS=1 \ OMP_NUM_THREADS=1 \ NUMEXPR_MAX_THREADS=1 COPY --from=freesurfer/synthstrip@sha256:f19578e5f033f2c707fa66efc8b3e11440569facb46e904b45fd52f1a12beb8b /freesurfer/models/synthstrip.1.pt /opt/freesurfer/models/synthstrip.1.pt ENV FREESURFER_HOME=/opt/freesurfer RUN apt update && apt install --no-install-recommends -y libtiff5 libpng16-16 COPY --from=mrtrix3/mrtrix3:3.0.4 /opt/mrtrix3/bin/dwidenoise /usr/local/bin COPY --from=mrtrix3/mrtrix3:3.0.4 /opt/mrtrix3/lib/libmrtrix.so /usr/local/lib # Container Sentinel ENV IS_DOCKER_8395080871=1 # Create a shared $HOME directory RUN useradd -m -s /bin/bash -G users mriqc WORKDIR /home/mriqc ENV HOME="/home/mriqc" # Pacify datalad RUN git config --global user.name "NiPreps - MRIQC" \ && git config --global user.email "nipreps@gmail.com" RUN micromamba shell init -s bash ENV PATH="${CONDA_PATH}/bin:$PATH" \ CPATH="${CONDA_PATH}/include:$CPATH" \ LD_LIBRARY_PATH="${CONDA_PATH}/lib:$LD_LIBRARY_PATH" # Refresh linked libraries RUN echo "${CONDA_PATH}/lib" > /etc/ld.so.conf.d/conda.conf \ && ldconfig # Installing dev requirements (packages that are not in pypi) WORKDIR /src/ # Precaching atlases RUN python -c "from templateflow import api as tfapi; \ tfapi.get('MNI152NLin2009cAsym', resolution=[1, 2], suffix=['T1w', 'T2w'], desc=None); \ tfapi.get('MNI152NLin2009cAsym', resolution=[1, 2], suffix='mask',\ desc=['brain', 'head']); \ tfapi.get('MNI152NLin2009cAsym', resolution=1, suffix='dseg', desc='carpet'); \ tfapi.get('MNI152NLin2009cAsym', resolution=1, suffix='probseg',\ label=['CSF', 'GM', 'WM']);\ tfapi.get('MNI152NLin2009cAsym', resolution=[1, 2], suffix='boldref')" # Installing MRIQC COPY --from=src /src/dist/*.whl . RUN pip install --no-cache-dir $( ls *.whl )[container,rodents,test] RUN find $HOME -type d -exec chmod go=u {} + && \ find $HOME -type f -exec chmod go=u {} + && \ rm -rf $HOME/.npm $HOME/.conda $HOME/.empty # Best practices RUN ldconfig # Update version RUN export VERSION=$(python -m mriqc --version | awk '{print $NF}') \ && echo "VERSION=$VERSION" >> /etc/environment WORKDIR /tmp/ # Run mriqc by default ENTRYPOINT ["/opt/conda/bin/mriqc"] ARG BUILD_DATE ARG VCS_REF LABEL org.label-schema.build-date=$BUILD_DATE \ org.label-schema.name="MRIQC" \ org.label-schema.description="MRIQC - Automated Quality Control and visual reports for Quality Assessment of structural (T1w, T2w) and functional MRI of the brain" \ org.label-schema.url="https://mriqc.readthedocs.io" \ org.label-schema.vcs-ref=$VCS_REF \ org.label-schema.vcs-url="https://github.com/nipreps/mriqc" \ org.label-schema.version=$VERSION \ org.label-schema.schema-version="1.0" ================================================ FILE: Dockerfile_devel ================================================ FROM nipreps/mriqc:latest COPY . /src/mriqc ARG VERSION RUN export SETUPTOOLS_SCM_PRETEND_VERSION=$VERSION && pip install -e /src/mriqc[all] ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2020 The NiPreps Developers Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: MANIFEST.in ================================================ #documentation recursive-exclude .circleci/ * recursive-exclude .github/ * recursive-exclude .git/ * recursive-exclude .maint/ * recursive-exclude docker/ * recursive-exclude docs/ * recursive-exclude test/ * exclude .* exclude Dockerfile exclude Dockerfile_devel exclude Makefile #data recursive-include mriqc/data * ================================================ FILE: Makefile ================================================ # Basic actions TEST_PATH = ./ MRIQC_VERSION = latest .PHONY: clean-pyc clean-pyc: find . -name '__pycache__' -type d -exec rm -r {} + find . -name '*.pyc' -exec rm --force {} + find . -name '*.pyo' -exec rm --force {} + find . -name '*~' -exec rm --force {} + .PHONY: clean-build clean-build: rm --force --recursive build/ rm --force --recursive dist/ rm --force --recursive *.egg-info rm --force --recursive src/ .PHONY: tag tag: git tag -a $(VERSION) -m "Version ${VERSION}" git push origin $(VERSION) git push upstream $(VERSION) lint: pylint ./mriqc/ .PHONY: test test: clean-pyc py.test --ignore=src/ --verbose $(TEST_PATH) dist: clean-build clean-pyc python setup.py sdist .PHONY: docker docker: docker build -t nipreps/mriqc:$(MRIQC_VERSION) \ --build-arg BUILD_DATE=`date -u +"%Y-%m-%dT%H:%M:%SZ"` \ --build-arg VCS_REF=`git rev-parse --short HEAD` \ --build-arg VERSION=`python setup.py --version` . .PHONY: release release: clean-build tag docker python setup.py sdist twine upload dist/* singularity: docker mkdir -p build/singularity docker run --privileged -ti --rm \ -v /var/run/docker.sock:/var/run/docker.sock \ -v $(shell pwd)/build/singularity:/output \ singularityware/docker2singularity \ nipreps/mriqc:$(MRIQC_VERSION) ================================================ FILE: README.rst ================================================ mriqc: image quality metrics for quality assessment of MRI ========================================================== |DOI| |Zenodo| |Package| |Pythons| |DevStatus| |PackageBuild| |License| |Documentation| |CircleCI| |EOSS| MRIQC extracts no-reference IQMs (image quality metrics) from structural (T1w and T2w), functional and diffusion MRI (magnetic resonance imaging) data. MRIQC is an open-source project, developed under the following software engineering principles: #. **Modularity and integrability**: MRIQC implements a `nipype `_ workflow to integrate modular sub-workflows that rely upon third party software toolboxes such as ANTs and AFNI. #. **Minimal preprocessing**: the MRIQC workflows should be as minimal as possible to estimate the IQMs on the original data or their minimally processed derivatives. #. **Interoperability and standards**: MRIQC follows the the `brain imaging data structure (BIDS) `_, and it adopts the `BIDS-App `_ standard. #. **Reliability and robustness**: the software undergoes frequent vetting sprints by testing its robustness against data variability (acquisition parameters, physiological differences, etc.) using images from `OpenfMRI `_. Its reliability is permanently checked and maintained with `CircleCI `_. Citation -------- .. topic:: **When using MRIQC, please include the following citation:** Esteban O, Birman D, Schaer M, Koyejo OO, Poldrack RA, Gorgolewski KJ; *MRIQC: Advancing the Automatic Prediction of Image Quality in MRI from Unseen Sites*; PLOS ONE 12(9):e0184661; doi:`10.1371/journal.pone.0184661 `_. Support and communication ------------------------- The documentation of this project is found here: https://mriqc.readthedocs.io/. Users can get help using the `mriqc-users google group `_. All bugs, concerns and enhancement requests for this software can be submitted here: https://github.com/nipreps/mriqc/issues. Development ----------- A local development build based on the latest docker build of MRIQC can be built with this command run from the root of this repository:: docker build -f Dockerfile_devel -t mriqc_devel . To test changes the local source code will need to be mounted into the development container:: docker run --rm -v .:/src/mriqc mriqc_devel New Python dependencies can be added in ``pyproject.toml`` under ``dependencies``. Any time a dependency is changed or added there the docker image will need to be rebuilt using the above ``docker build`` command. License information ------------------- *MRIQC* adheres to the `general licensing guidelines `__ of the *NiPreps framework*. *MRIQC* originally derives from, and hence is heavily influenced by, the `PCP Quality Assessment Protocol `__. Please check the ``NOTICE`` file for further information. License ~~~~~~~ Copyright (c) 2021, the *NiPreps* Developers. As of the 21.0.x pre-release and release series, *MRIQC* is licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at `http://www.apache.org/licenses/LICENSE-2.0 `__. Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Acknowledgements ---------------- This work is steered and maintained by the `NiPreps Community `__. The development of this resource was supported by the Laura and John Arnold Foundation (RAP and KJG), the NIBIB (R01EB020740, SSG; 1P41EB019936-01A1SSG, YOH), the NIMH (RF1MH121867, RAP, OE; R24MH114705 and R24MH117179, RAP; 1RF1MH121885 SSG), NINDS (U01NS103780, RAP), and NSF (CRCNS 1912266, YOH). OE acknowledges financial support from the SNSF Ambizione project “*Uncovering the interplay of structure, function, and dynamics of brain connectivity using MRI*” (grant number `PZ00P2_185872 `__). .. topic:: **Thanks** * The QAP developers (C. Craddock, S. Giavasis, D. Clark, Z. Shezhad, and J. Pellman) for the initial base of code which MRIQC was forked from. * W Triplett and CA Moodie for their initial contributions with bugfixes and documentation, and * J Varada for his contributions on the source code. .. |DOI| image:: https://img.shields.io/badge/doi-10.1371%2Fjournal.pone.0184661-blue.svg :target: https://doi.org/10.1371/journal.pone.0184661 .. |Zenodo| image:: https://zenodo.org/badge/DOI/10.5281/zenodo.2630889.svg :target: https://doi.org/10.5281/zenodo.2630889 .. |Package| image:: https://img.shields.io/pypi/v/mriqc.svg :target: https://pypi.python.org/pypi/mriqc/ .. |Pythons| image:: https://img.shields.io/pypi/pyversions/mriqc.svg :target: https://pypi.python.org/pypi/mriqc/ .. |DevStatus| image:: https://img.shields.io/pypi/status/mriqc.svg :target: https://pypi.python.org/pypi/mriqc/ .. |PackageBuild| image:: https://github.com/nipreps/mriqc/actions/workflows/pythonpackage.yml/badge.svg :target: https://github.com/nipreps/mriqc/actions/workflows/pythonpackage.yml .. |License| image:: https://img.shields.io/pypi/l/mriqc.svg :target: https://pypi.python.org/pypi/mriqc/ .. |Documentation| image:: https://readthedocs.org/projects/mriqc/badge/?version=latest :target: http://mriqc.readthedocs.io/en/latest/?badge=latest .. |CircleCI| image:: https://circleci.com/gh/nipreps/mriqc/tree/master.svg?style=shield :target: https://circleci.com/gh/nipreps/mriqc/tree/master .. |EOSS| image:: https://chanzuckerberg.github.io/open-science/badges/CZI-EOSS.svg :target: https://czi.co/EOSS :alt: CZI's Essential Open Source Software for Science ================================================ FILE: docker/files/neurodebian.gpg ================================================ -----BEGIN PGP PUBLIC KEY BLOCK----- Version: GnuPG v1 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 =fAJZ -----END PGP PUBLIC KEY BLOCK----- ================================================ FILE: docs/Makefile ================================================ # Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build PYTHONPATH = $(PWD) # User-friendly check for sphinx-build ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) endif # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source .PHONY: help help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " applehelp to make an Apple Help Book" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " xml to make Docutils-native XML files" @echo " pseudoxml to make pseudoxml-XML files for display purposes" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" @echo " coverage to run coverage check of the documentation (if enabled)" .PHONY: clean clean: rm -rf $(BUILDDIR)/* .PHONY: html html: PYTHONPATH=$(PYTHONPATH) $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." .PHONY: dirhtml dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." .PHONY: singlehtml singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." .PHONY: pickle pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." .PHONY: json json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." .PHONY: htmlhelp htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." .PHONY: qthelp qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/mriqc.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/mriqc.qhc" .PHONY: applehelp applehelp: $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp @echo @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." @echo "N.B. You won't be able to view it unless you put it in" \ "~/Library/Documentation/Help or install it in your application" \ "bundle." .PHONY: devhelp devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/mriqc" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/mriqc" @echo "# devhelp" .PHONY: epub epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." .PHONY: latex latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." .PHONY: latexpdf latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." .PHONY: latexpdfja latexpdfja: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through platex and dvipdfmx..." $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." .PHONY: text text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." .PHONY: man man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." .PHONY: texinfo texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." .PHONY: info info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." .PHONY: gettext gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." .PHONY: changes changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." .PHONY: linkcheck linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." .PHONY: doctest doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." .PHONY: coverage coverage: $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage @echo "Testing of coverage in the sources finished, look at the " \ "results in $(BUILDDIR)/coverage/python.txt." .PHONY: xml xml: $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml @echo @echo "Build finished. The XML files are in $(BUILDDIR)/xml." .PHONY: pseudoxml pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." ================================================ FILE: docs/notebooks/.gitignore ================================================ .ipynb_checkpoints/ ABIDE-BIDS/ data/ example_artifacts_dataset/ out/ temp/ *.svg ================================================ FILE: docs/notebooks/MRIQC Web API.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Querying the MRIQC Web API\n", "\n", "This notebook shows how the web-API can be leveraged to analyze the image quality metrics (IQMs) that have been extracted with MRIQC\n", "\n", "This notebook is a derivative work of https://gist.github.com/chrisfilo/eccdb8b98f8e74d24a3395a49fbadf03" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import json\n", "import multiprocessing as mp\n", "import urllib.request\n", "from json import load\n", "\n", "import numpy as np\n", "import pandas as pd\n", "import pylab as plt\n", "import seaborn as sns\n", "from pandas.io.json import json_normalize\n", "\n", "%matplotlib inline" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Preparation\n", "\n", "Let's define a function that will query the appropriate endpoint and a helper function to plot some distributions (at the bottom of this notebook)." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "def get_iqms(modality, versions=None, software='mriqc'):\n", " \"\"\"\n", " Grab all iqms for the given modality and the list of versions\n", " \"\"\"\n", " url_root = 'https://mriqc.nimh.nih.gov/api/v1/{modality}?{query}'\n", " page = 1\n", " dfs = []\n", "\n", " if versions is None:\n", " versions = ['*']\n", "\n", " for version in versions:\n", " while True:\n", " query = []\n", "\n", " if software is not None:\n", " query.append(f'\"provenance.software\":\"{software}\"')\n", "\n", " if version != '*':\n", " query.append(f'\"provenance.version\":\"{version}\"')\n", "\n", " where = ','.join(query)\n", " page_url = url_root.format(modality=modality, query=f'where={where}&page={page}')\n", " with urllib.request.urlopen(page_url) as url:\n", " data = json.loads(url.read().decode())\n", " dfs.append(json_normalize(data['_items']))\n", " if 'next' not in data['_links'].keys():\n", " break\n", " else:\n", " page += 1\n", "\n", " # Compose a pandas dataframe\n", " return pd.concat(dfs, ignore_index=True)\n", "\n", "\n", "def plot_measure(data, xlabel=None, label=None, ax=None, min=None, max=None):\n", " \"\"\"\n", " Distribution plot of a given measure\n", " \"\"\"\n", " sns.distplot(data, ax=ax, label=label)\n", "\n", " if xlabel is not None:\n", " ax.set_xlabel(xlabel)\n", "\n", " if min is None:\n", " min = np.percentile(data, 0.5)\n", "\n", " if max is None:\n", " max = np.percentile(data, 99.5)\n", " ax.set_xlim((min, max))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Fetch IQMs\n", "\n", "Let's fetch IQMs for the two principal modalities of MRIQC, T1-weighted images and BOLD-fMRI. Filter out repeated images to obtain unique records." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "# T1\n", "df_t1w = get_iqms('T1w', software=None)\n", "df_t1w_unique = df_t1w.drop_duplicates(subset=['provenance.md5sum'])\n", "\n", "# BOLD\n", "df_bold = get_iqms('bold')\n", "df_bold_unique = df_bold.drop_duplicates(subset=['provenance.md5sum'])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Evolution of IQMs submission\n", "\n", "This code generates Figure 3A of the abstract" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "image/png": "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\nQmCC\n", "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "import datetime\n", "\n", "import matplotlib.dates as mdates\n", "from dateutil import parser\n", "\n", "dates_t1w = [parser.parse(d) for d in df_t1w['_created'].values]\n", "dates_t1w.sort()\n", "\n", "dates_t1w_u = [parser.parse(d) for d in df_t1w_unique['_created'].values]\n", "dates_t1w_u.sort()\n", "# mindate = dates_t1w[0]\n", "ax = plt.subplot(111)\n", "ax.plot(dates_t1w, list(range(1, len(dates_t1w) + 1)), label='total')\n", "ax.plot(dates_t1w_u, list(range(1, len(dates_t1w_u) + 1)), label='unique')\n", "ax.set_title('Number of T1w records in database')\n", "ax.legend()\n", "\n", "plt.savefig('fig03a-0.svg', bbox_inches='tight', transparent=False, pad_inches=0)" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "dates_bold = [parser.parse(d) for d in df_bold['_created'].values]\n", "dates_bold.sort()\n", "dates_bold_u = [parser.parse(d) for d in df_bold_unique['_created'].values]\n", "dates_bold_u.sort()\n", "# mindate = dates_t1w[0]\n", "ax = plt.subplot(111)\n", "ax.plot(dates_bold, list(range(1, len(dates_bold) + 1)), label='total')\n", "ax.plot(dates_bold_u, list(range(1, len(dates_bold_u) + 1)), label='unique')\n", "ax.set_title('Number of BOLD records in database')\n", "ax.legend()\n", "plt.savefig('fig03a-1.svg', bbox_inches='tight', transparent=False, pad_inches=0)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Playing with T1w IQMs\n", "\n", "Let's plot some of the IQMs for the T1w modality. First, let's check the names of the IQMs. These measures are explained in the documentation (http://mriqc.readthedocs.io/en/stable/iqms/t1w.html)" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "cjv,cnr,efc,fber,fwhm_avg,fwhm_x,fwhm_y,fwhm_z,icvs_csf,icvs_gm,icvs_wm,inu_med,inu_range,qi_1,qi_2,rpve_csf,rpve_gm,rpve_wm,size_x,size_y,size_z,snr_csf,snr_gm,snr_total,snr_wm,snrd_csf,snrd_gm,snrd_total,snrd_wm,spacing_x,spacing_y,spacing_z,summary_bg_k,summary_bg_mad,summary_bg_mean,summary_bg_median,summary_bg_n,summary_bg_p05,summary_bg_p95,summary_bg_stdv,summary_csf_k,summary_csf_mad,summary_csf_mean,summary_csf_median,summary_csf_n,summary_csf_p05,summary_csf_p95,summary_csf_stdv,summary_gm_k,summary_gm_mad,summary_gm_mean,summary_gm_median,summary_gm_n,summary_gm_p05,summary_gm_p95,summary_gm_stdv,summary_wm_k,summary_wm_mad,summary_wm_mean,summary_wm_median,summary_wm_n,summary_wm_p05,summary_wm_p95,summary_wm_stdv,tpm_overlap_csf,tpm_overlap_gm,tpm_overlap_wm,wm2max\n" ] } ], "source": [ "print(\n", " ','.join(\n", " [\n", " line\n", " for line in df_t1w.columns\n", " if not line.startswith('_')\n", " and not line.startswith('bids_meta')\n", " and not line.startswith('provenance')\n", " ]\n", " )\n", ")" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "f, ax = plt.subplots(1, 5, figsize=(25, 5))\n", "plot_measure(df_t1w_unique.cjv, xlabel='Coefficient of joint variation (CJV)', ax=ax[0])\n", "plot_measure(df_t1w_unique.cnr, xlabel='Contrast-to-noise ratio (CNR)', ax=ax[1])\n", "plot_measure(\n", " df_t1w_unique.snr_wm,\n", " xlabel='Signal-to-noise ratio estimated on the white-matter (SNR)',\n", " ax=ax[2],\n", ")\n", "plot_measure(df_t1w_unique.fwhm_avg, xlabel='Smoothness (FWHM)', ax=ax[3])\n", "plot_measure(df_t1w_unique.wm2max, xlabel='WM-to-max intensity ratio (WM2MAX)', ax=ax[4])\n", "plt.suptitle('Distributions of some IQMs extracted from T1-weighted MRI')\n", "plt.savefig('fig03b-0.svg', bbox_inches='tight', transparent=False, pad_inches=0)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Playing with BOLD IQMs\n", "\n", "Let's plot some of the IQMs for the BOLD modality. First, let's check the names of the IQMs. These measures are explained in the documentation (http://mriqc.readthedocs.io/en/stable/iqms/bold.html)" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "aor,aqi,dummy_trs,dvars_nstd,dvars_std,dvars_vstd,efc,fber,fd_mean,fd_num,fd_perc,fwhm_avg,fwhm_x,fwhm_y,fwhm_z,gcor,gsr_x,gsr_y,size_t,size_x,size_y,size_z,snr,spacing_tr,spacing_x,spacing_y,spacing_z,summary_bg_k,summary_bg_mad,summary_bg_mean,summary_bg_median,summary_bg_n,summary_bg_p05,summary_bg_p95,summary_bg_stdv,summary_fg_k,summary_fg_mad,summary_fg_mean,summary_fg_median,summary_fg_n,summary_fg_p05,summary_fg_p95,summary_fg_stdv,tsnr\n" ] } ], "source": [ "print(\n", " ','.join(\n", " [\n", " line\n", " for line in df_bold.columns\n", " if not line.startswith('_')\n", " and not line.startswith('bids_meta')\n", " and not line.startswith('provenance')\n", " ]\n", " )\n", ")" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "f, ax = plt.subplots(1, 5, figsize=(25, 5))\n", "plot_measure(\n", " df_bold_unique[df_bold_unique.fd_mean < 10].fd_mean,\n", " xlabel='Framewise Displacement (mm)',\n", " max=2,\n", " ax=ax[0],\n", ")\n", "plot_measure(df_bold_unique[df_bold_unique.dvars_nstd < 100].dvars_nstd, xlabel='DVARS', ax=ax[1])\n", "plot_measure(df_bold_unique.gcor, xlabel='Global correlation', ax=ax[2])\n", "plot_measure(df_bold_unique.gsr_x, label='x-axis', ax=ax[3])\n", "plot_measure(df_bold_unique.gsr_y, xlabel='Ghost-to-signal ratio (GSR)', label='y-axis', ax=ax[3])\n", "ax[3].legend()\n", "plot_measure(df_bold_unique.tsnr, xlabel='Temporal SNR (tSNR)', ax=ax[4])\n", "plt.suptitle('Distributions of some IQMs extracted from BOLD fMRI')\n", "plt.savefig(\n", " 'fig03b-1.png', bbox_inches='tight', transparent=False, pad_inches=0, facecolor='white'\n", ")" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.1" } }, "nbformat": 4, "nbformat_minor": 2 } ================================================ FILE: docs/notebooks/Paper-v1.0.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "%matplotlib inline\n", "%load_ext autoreload\n", "%autoreload 2\n", "import os.path as op\n", "\n", "import matplotlib.pyplot as plt\n", "import numpy as np\n", "import pandas as pd\n", "import seaborn as sns\n", "\n", "sns.set(style='whitegrid')" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "from mriqc.classifier import data as mcd\n", "\n", "abide, _ = mcd.read_dataset(x_path, y_path, rate_label='rater_1')\n", "sites = sorted(set(abide.site.values.ravel()))\n", "\n", "fmt = r'{site} & \\pixmat{{{size[0]:d}$\\pm${sr[0]:d}}}{{{size[1]:d}$\\pm${sr[1]:d}}}{{{size[2]:d}$\\pm${sr[1]:d}}}'\n", "fmt += r'& \\pixmat[mm]{{{sp[0]:.2f}$\\pm${spr[0]:.2f}}}{{{sp[1]:.2f}$\\pm${spr[1]:.2f}}}{{{sp[2]:.2f}$\\pm${spr[1]:.2f}}}'\n", "\n", "\n", "for site in sites:\n", " subabide = abide.loc[abide.site.str.contains(site)]\n", "\n", " medians = np.median(\n", " subabide[['size_x', 'size_y', 'size_z', 'spacing_x', 'spacing_y', 'spacing_z']], axis=0\n", " )\n", "\n", " mins = np.abs(\n", " medians\n", " - np.min(\n", " subabide[['size_x', 'size_y', 'size_z', 'spacing_x', 'spacing_y', 'spacing_z']], axis=0\n", " )\n", " )\n", "\n", " maxs = np.abs(\n", " medians\n", " - np.max(\n", " subabide[['size_x', 'size_y', 'size_z', 'spacing_x', 'spacing_y', 'spacing_z']], axis=0\n", " )\n", " )\n", "\n", " ranges = np.max(np.vstack((maxs, mins)), axis=0)\n", "\n", " print(\n", " fmt.format(\n", " site=site,\n", " size=tuple(medians[:3].astype(int)),\n", " sr=tuple(ranges[:3].astype(int)),\n", " sp=tuple(medians[3:]),\n", " spr=tuple(ranges[3:]),\n", " )\n", " )" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "# data_path = '/home/oesteban/Google Drive/mriqc'\n", "data_path = '/home/oesteban/tmp/mriqc-ml-tests-2/'\n", "out_path = data_path\n", "loso = pd.read_csv(op.join(data_path, 'cv_loso_inner.csv'), index_col=False)\n", "kfold = pd.read_csv(op.join(data_path, 'cv_kfold_inner.csv'), index_col=False)\n", "\n", "kfold_outer = pd.read_csv(op.join(data_path, 'cv_kfold_outer.csv'), index_col=False)\n", "loso_outer = pd.read_csv(op.join(data_path, 'cv_loso_outer.csv'), index_col=False)\n", "\n", "\n", "def gen_newparams(dataframe):\n", " thisdf = dataframe.copy()\n", " thisdf['zscored_str'] = ['nzs'] * len(thisdf['zscored'])\n", " thisdf.loc[thisdf.zscored == 1, 'zscored_str'] = 'zs'\n", " thisdf['params'] = thisdf['clf'] + '-' + thisdf['zscored_str'] + ' ' + thisdf['params']\n", " del thisdf['zscored_str']\n", " return thisdf\n", "\n", "\n", "loso = gen_newparams(loso)\n", "kfold = gen_newparams(kfold)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "loso_models_list = list(set(loso.params.values.ravel().tolist()))\n", "kfold_models_list = list(set(kfold.params.values.ravel().tolist()))\n", "\n", "best_param = {}\n", "\n", "spstr = ['LoSo', '10-fold']\n", "best_models = {}\n", "for i, split_cv in enumerate([loso, kfold]):\n", " best_models[spstr[i]] = {}\n", " splitcols = [col for col in split_cv.columns.ravel() if col.startswith('split0')]\n", " for clf in [\n", " 'svc_linear-nzs',\n", " 'svc_rbf-nzs',\n", " 'rfc-nzs',\n", " 'svc_linear-zs',\n", " 'svc_rbf-zs',\n", " 'rfc-zs',\n", " ]:\n", " thismodeldf = split_cv.loc[split_cv.params.str.contains(clf)]\n", " max_auc = thismodeldf.mean_auc.max()\n", " best = thismodeldf.loc[thismodeldf.mean_auc >= max_auc]\n", " best_list = best.params.values.ravel().tolist()\n", "\n", " if len(best_list) == 1:\n", " best_models[spstr[i]][clf] = best_list[0]\n", " else:\n", " overall_means = [\n", " thismodeldf.loc[thismodeldf.params.str.contains(pset), 'mean_auc'].mean()\n", " for pset in best_list\n", " ]\n", " overall_max = np.max(overall_means)\n", " if sum([val >= overall_max for val in overall_means]) == 1:\n", " best_models[spstr[i]][clf] = best_list[np.argmax(overall_means)]\n", " else:\n", " best_models[spstr[i]][clf] = best_list[0]\n", "\n", "newdict = {'AUC': [], 'Classifier': [], 'Split scheme': []}\n", "\n", "modelnames = {\n", " 'rfc-nzs': 'RFC-nzs',\n", " 'rfc-zs': 'RFC-zs',\n", " 'svc_linear-nzs': 'SVC_lin-nzs',\n", " 'svc_linear-zs': 'SVC_lin-zs',\n", " 'svc_rbf-nzs': 'SVC_rbf-nzs',\n", " 'svc_rbf-zs': 'SVC_rbf-zs',\n", "}\n", "\n", "for key, val in list(best_models['LoSo'].items()):\n", " scores = loso.loc[loso.params.str.contains(val), 'mean_auc'].values.ravel().tolist()\n", " nscores = len(scores)\n", "\n", " newdict['AUC'] += scores\n", " newdict['Classifier'] += [modelnames[key]] * nscores\n", " newdict['Split scheme'] += ['LoSo (16 folds)'] * nscores\n", "\n", "for key, val in list(best_models['10-fold'].items()):\n", " scores = kfold.loc[kfold.params.str.contains(val), 'mean_auc'].values.ravel().tolist()\n", " nscores = len(scores)\n", "\n", " newdict['AUC'] += scores\n", " newdict['Classifier'] += [modelnames[key]] * nscores\n", " newdict['Split scheme'] += ['10-fold'] * nscores\n", "\n", "newdf = pd.DataFrame(newdict).sort_values(by=['Split scheme', 'Classifier'])" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "def plot_cv_outer(\n", " data, score='auc', zscored=0, ax=None, ds030_score=None, split_type='LoSo', color='dodgerblue'\n", "):\n", " if ax is None:\n", " ax = plt.gca()\n", "\n", " outer_score = data.loc[data[score].notnull(), [score, 'zscored']]\n", " sns.distplot(\n", " outer_score.loc[outer_score.zscored == zscored, score],\n", " hist=True,\n", " norm_hist=True,\n", " ax=ax,\n", " color=color,\n", " label=split_type,\n", " )\n", " ax.set_xlim([0.4, 1.0])\n", " ax.grid(False)\n", " ax.set_yticklabels([])\n", "\n", " mean = outer_score.loc[outer_score.zscored == zscored, score].mean()\n", " std = outer_score.loc[outer_score.zscored == zscored, score].std()\n", "\n", " mean_coord = draw_line(mean, ax=ax, color=color, lw=2.0, marker='o', extend=True)\n", "\n", " ymax = ax.get_ylim()[1]\n", " draw_line(mean - std, ax=ax, color=color, extend=True)\n", " draw_line(mean + std, ax=ax, color=color, extend=True)\n", "\n", " ax.annotate(\n", " rf'$\\mu$={mean:0.3f}',\n", " xy=(mean_coord[0], 0.75 * ymax),\n", " xytext=(-35, 30),\n", " textcoords='offset points',\n", " va='center',\n", " color='w',\n", " size=14,\n", " bbox={'boxstyle': 'round', 'fc': color, 'ec': 'none', 'color': 'none', 'lw': 0},\n", " arrowprops={\n", " 'arrowstyle': 'wedge,tail_width=0.8',\n", " 'lw': 0,\n", " 'patchA': None,\n", " 'patchB': None,\n", " 'fc': color,\n", " 'ec': 'none',\n", " 'relpos': (0.5, 0.5),\n", " },\n", " )\n", " sigmay = 0.70 * ymax\n", " ax.annotate(\n", " s='',\n", " xy=(mean - std, sigmay),\n", " xytext=(mean + std, sigmay),\n", " arrowprops={'arrowstyle': '<->'},\n", " )\n", " ax.annotate(\n", " r'$2\\sigma$=%0.3f' % (2 * std),\n", " xy=(mean_coord[0], 0.70 * ymax),\n", " xytext=(-25, -12),\n", " textcoords='offset points',\n", " va='center',\n", " color='k',\n", " size=12,\n", " bbox={\n", " 'boxstyle': 'round',\n", " 'fc': 'w',\n", " 'ec': 'none',\n", " 'color': 'none',\n", " 'alpha': 0.7,\n", " 'lw': 0,\n", " },\n", " )\n", "\n", " if ds030_score is not None:\n", " ds030_coord = draw_line(ds030_score, ax=ax, color='k', marker='o')\n", " ax.annotate(\n", " 'DS030',\n", " xy=ds030_coord,\n", " xytext=(-100, 0),\n", " textcoords='offset points',\n", " va='center',\n", " color='w',\n", " size=16,\n", " bbox={'boxstyle': 'round', 'fc': color, 'ec': 'none', 'color': 'none', 'lw': 0},\n", " arrowprops={\n", " 'arrowstyle': 'wedge,tail_width=0.8',\n", " 'lw': 0,\n", " 'patchA': None,\n", " 'patchB': None,\n", " 'fc': color,\n", " 'ec': 'none',\n", " 'relpos': (0.5, 0.5),\n", " },\n", " )\n", "\n", "\n", "def draw_line(score, ax=None, color='k', marker=None, lw=0.7, extend=False):\n", " if ax is None:\n", " ax = plt.gca()\n", "\n", " if score > 1.0:\n", " score = 1.0\n", "\n", " coords = [score, -1]\n", " pdf_points = ax.lines[0].get_data()\n", " coords[1] = np.interp([coords[0]], pdf_points[0], pdf_points[1])\n", "\n", " if extend:\n", " ax.axvline(coords[0], ymin=coords[1] / ax.get_ylim()[1], ymax=0.75, color='gray', lw=0.7)\n", "\n", " ax.axvline(\n", " coords[0],\n", " ymin=coords[1] / ax.get_ylim()[1],\n", " ymax=0,\n", " color=color,\n", " marker=marker,\n", " markevery=2,\n", " markeredgewidth=1.5,\n", " markerfacecolor='w',\n", " markeredgecolor=color,\n", " lw=lw,\n", " )\n", "\n", " return coords" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "sns.set(style='whitegrid')\n", "\n", "fig = plt.figure(figsize=(20, 8))\n", "ax1 = plt.subplot2grid((2, 4), (0, 0), colspan=2, rowspan=2)\n", "\n", "sns.violinplot(\n", " x='Classifier',\n", " y='AUC',\n", " hue='Split scheme',\n", " data=newdf,\n", " split=True,\n", " palette=['dodgerblue', 'darkorange'],\n", " ax=ax1,\n", ")\n", "ax1.set_ylim([0.70, 1.0])\n", "ax1.set_ylabel('AUC')\n", "ax1.set_xlabel('Model')\n", "ax1.set_title('Model selection - Inner loop of nested cross-validation')\n", "\n", "ax2 = plt.subplot2grid((2, 4), (0, 2))\n", "plot_cv_outer(kfold_outer, zscored=0, score='auc', ax=ax2, ds030_score=0.695, split_type='10-fold')\n", "ax2.set_xlabel('')\n", "ax2.legend()\n", "ax2.set_title('Evaluation - Outer loop of nested cross-validation')\n", "ax2.title.set_position([1.1, 1.0])\n", "\n", "ax3 = plt.subplot2grid((2, 4), (1, 2))\n", "plot_cv_outer(\n", " loso_outer,\n", " zscored=0,\n", " score='auc',\n", " ax=ax3,\n", " ds030_score=0.695,\n", " color='darkorange',\n", " split_type='LoSo (17 folds)',\n", ")\n", "ax3.legend()\n", "ax3.set_xlabel('AUC')\n", "\n", "ax4 = plt.subplot2grid((2, 4), (0, 3))\n", "plot_cv_outer(\n", " kfold_outer, zscored=0, score='acc', ax=ax4, ds030_score=0.7283, split_type='10-fold'\n", ")\n", "ax4.set_xlabel('')\n", "ax4.legend()\n", "\n", "ax5 = plt.subplot2grid((2, 4), (1, 3))\n", "plot_cv_outer(\n", " loso_outer,\n", " zscored=0,\n", " score='acc',\n", " ax=ax5,\n", " ds030_score=0.7283,\n", " color='darkorange',\n", " split_type='LoSo (17 folds)',\n", ")\n", "ax5.legend()\n", "ax5.set_xlabel('Accuracy')\n", "\n", "\n", "fig.savefig(op.join(out_path, 'crossvalidation.pdf'), bbox_inches='tight', pad_inches=0, dpi=300)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "zscoreddf = loso_outer.loc[loso_outer.zscored == 0, ['auc', 'acc', 'site']]\n", "palette = sns.color_palette('cubehelix', len(set(zscoreddf.site)))\n", "sns.pairplot(\n", " zscoreddf.loc[zscoreddf.auc.notnull(), ['auc', 'acc', 'site']], hue='site', palette=palette\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "sites = sorted(set(loso_outer.site.ravel().tolist()))\n", "palette = sns.color_palette('husl', len(sites))\n", "fig = plt.figure()\n", "for i, site in enumerate(sites):\n", " sitedf = loso_outer.loc[loso_outer.site == site]\n", " accdf = sitedf.loc[sitedf.zscored == 0]\n", " sns.distplot(accdf.acc.values.ravel(), bins=20, kde=0, label=site, color=palette[i])\n", "\n", "fig.gca().legend()\n", "fig.gca().set_xlim([0.5, 1.0])" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.1" } }, "nbformat": 4, "nbformat_minor": 2 } ================================================ FILE: docs/notebooks/Paper-v2.0.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Generation of tables and figures of MRIQC paper\n", "\n", "This notebook is associated to the paper:\n", "\n", "Esteban O, Birman D, Schaer M, Koyejo OO, Poldrack RA, Gorgolewski KJ; MRIQC: Predicting Quality in Manual MRI Assessment Protocols Using No-Reference Image Quality Measures; bioRxiv 111294; doi:[10.1101/111294](https://doi.org/10.1101/111294)." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true, "scrolled": true }, "outputs": [], "source": [ "%matplotlib inline\n", "%load_ext autoreload\n", "%autoreload 2\n", "import os.path as op\n", "\n", "import numpy as np\n", "import pandas as pd\n", "from mriqc.classifier.data import combine_datasets, read_dataset\n", "from mriqc.viz import misc as mviz\n", "from pkg_resources import resource_filename as pkgrf\n", "\n", "# Where the outputs should be saved\n", "outputs_path = '../../mriqc-data/'\n", "\n", "# Path to ABIDE's BIDS structure\n", "abide_path = '/home/oesteban/Data/ABIDE/'\n", "# Path to DS030's BIDS structure\n", "ds030_path = '/home/oesteban/Data/ds030/'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Read some data (from mriqc package)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "x_path = pkgrf('mriqc', 'data/csv/x_abide.csv')\n", "y_path = pkgrf('mriqc', 'data/csv/y_abide.csv')\n", "ds030_x_path = pkgrf('mriqc', 'data/csv/x_ds030.csv')\n", "ds030_y_path = pkgrf('mriqc', 'data/csv/y_ds030.csv')\n", "\n", "rater_types = {'rater_1': float, 'rater_2': float, 'rater_3': float}\n", "mdata = pd.read_csv(y_path, index_col=False, dtype=rater_types)\n", "\n", "sites = sorted(set(mdata.site.values.ravel().tolist()))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Figure 1: artifacts in MRI\n", "Shows a couple of subpar datasets from the ABIDE dataset" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "out_file = op.join(outputs_path, 'figures', 'fig01-artifacts.svg')\n", "mviz.figure1(\n", " op.join(abide_path, 'sub-50137', 'anat', 'sub-50137_T1w.nii.gz'),\n", " op.join(abide_path, 'sub-50110', 'anat', 'sub-50110_T1w.nii.gz'),\n", " out_file,\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "out_file_pdf = out_file[:4] + '.pdf'\n", "!rsvg-convert -f pdf -o $out_file_pdf $out_file" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Figure 2: batch effects\n", "\n", "This code was use to generate the second figure" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "from mriqc.classifier.sklearn import preprocessing as mcsp\n", "\n", "# Concatenate ABIDE & DS030\n", "fulldata = combine_datasets(\n", " [\n", " (x_path, y_path, 'ABIDE'),\n", " (ds030_x_path, ds030_y_path, 'DS030'),\n", " ]\n", ")\n", "\n", "# Names of all features\n", "features = [\n", " 'cjv',\n", " 'cnr',\n", " 'efc',\n", " 'fber',\n", " 'fwhm_avg',\n", " 'fwhm_x',\n", " 'fwhm_y',\n", " 'fwhm_z',\n", " 'icvs_csf',\n", " 'icvs_gm',\n", " 'icvs_wm',\n", " 'inu_med',\n", " 'inu_range',\n", " 'qi_1',\n", " 'qi_2',\n", " 'rpve_csf',\n", " 'rpve_gm',\n", " 'rpve_wm',\n", " 'size_x',\n", " 'size_y',\n", " 'size_z',\n", " 'snr_csf',\n", " 'snr_gm',\n", " 'snr_total',\n", " 'snr_wm',\n", " 'snrd_csf',\n", " 'snrd_gm',\n", " 'snrd_total',\n", " 'snrd_wm',\n", " 'spacing_x',\n", " 'spacing_y',\n", " 'spacing_z',\n", " 'summary_bg_k',\n", " 'summary_bg_mad',\n", " 'summary_bg_mean',\n", " 'summary_bg_median',\n", " 'summary_bg_n',\n", " 'summary_bg_p05',\n", " 'summary_bg_p95',\n", " 'summary_bg_stdv',\n", " 'summary_csf_k',\n", " 'summary_csf_mad',\n", " 'summary_csf_mean',\n", " 'summary_csf_median',\n", " 'summary_csf_n',\n", " 'summary_csf_p05',\n", " 'summary_csf_p95',\n", " 'summary_csf_stdv',\n", " 'summary_gm_k',\n", " 'summary_gm_mad',\n", " 'summary_gm_mean',\n", " 'summary_gm_median',\n", " 'summary_gm_n',\n", " 'summary_gm_p05',\n", " 'summary_gm_p95',\n", " 'summary_gm_stdv',\n", " 'summary_wm_k',\n", " 'summary_wm_mad',\n", " 'summary_wm_mean',\n", " 'summary_wm_median',\n", " 'summary_wm_n',\n", " 'summary_wm_p05',\n", " 'summary_wm_p95',\n", " 'summary_wm_stdv',\n", " 'tpm_overlap_csf',\n", " 'tpm_overlap_gm',\n", " 'tpm_overlap_wm',\n", " 'wm2max',\n", "]\n", "\n", "# Names of features that can be normalized\n", "coi = [\n", " 'cjv',\n", " 'cnr',\n", " 'efc',\n", " 'fber',\n", " 'fwhm_avg',\n", " 'fwhm_x',\n", " 'fwhm_y',\n", " 'fwhm_z',\n", " 'snr_csf',\n", " 'snr_gm',\n", " 'snr_total',\n", " 'snr_wm',\n", " 'snrd_csf',\n", " 'snrd_gm',\n", " 'snrd_total',\n", " 'snrd_wm',\n", " 'summary_csf_mad',\n", " 'summary_csf_mean',\n", " 'summary_csf_median',\n", " 'summary_csf_p05',\n", " 'summary_csf_p95',\n", " 'summary_csf_stdv',\n", " 'summary_gm_k',\n", " 'summary_gm_mad',\n", " 'summary_gm_mean',\n", " 'summary_gm_median',\n", " 'summary_gm_p05',\n", " 'summary_gm_p95',\n", " 'summary_gm_stdv',\n", " 'summary_wm_k',\n", " 'summary_wm_mad',\n", " 'summary_wm_mean',\n", " 'summary_wm_median',\n", " 'summary_wm_p05',\n", " 'summary_wm_p95',\n", " 'summary_wm_stdv',\n", "]\n", "\n", "# Plot batches\n", "fig = mviz.plot_batches(\n", " fulldata,\n", " cols=list(reversed(coi)),\n", " out_file=op.join(outputs_path, 'figures/fig02-batches-a.pdf'),\n", ")\n", "\n", "# Apply new site-wise scaler\n", "scaler = mcsp.BatchRobustScaler(by='site', columns=coi)\n", "scaled = scaler.fit_transform(fulldata)\n", "fig = mviz.plot_batches(\n", " scaled,\n", " cols=coi,\n", " site_labels='right',\n", " out_file=op.join(outputs_path, 'figures/fig02-batches-b.pdf'),\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Figure 3: Inter-rater variability\n", "\n", "In this figure we evaluate the inter-observer agreement between both raters on the 100 data points overlapping of ABIDE. Also the Cohen's Kappa is computed." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from sklearn.metrics import cohen_kappa_score\n", "\n", "overlap = mdata[np.all(~np.isnan(mdata[['rater_1', 'rater_2']]), axis=1)]\n", "y1 = overlap.rater_1.values.ravel().tolist()\n", "y2 = overlap.rater_2.values.ravel().tolist()\n", "fig = mviz.inter_rater_variability(\n", " y1, y2, out_file=op.join(outputs_path, 'figures', 'fig02-irv.pdf')\n", ")\n", "\n", "print(f\"Cohen's Kappa {cohen_kappa_score(y1, y2):f}\")\n", "\n", "y1 = overlap.rater_1.values.ravel()\n", "y1[y1 == 0] = 1\n", "\n", "y2 = overlap.rater_2.values.ravel()\n", "y2[y2 == 0] = 1\n", "print(f\"Cohen's Kappa (binarized): {cohen_kappa_score(y1, y2):f}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Figure 5: Model selection" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "import seaborn as sns\n", "\n", "rfc_acc = [\n", " 0.842,\n", " 0.815,\n", " 0.648,\n", " 0.609,\n", " 0.789,\n", " 0.761,\n", " 0.893,\n", " 0.833,\n", " 0.842,\n", " 0.767,\n", " 0.806,\n", " 0.850,\n", " 0.878,\n", " 0.798,\n", " 0.559,\n", " 0.881,\n", " 0.375,\n", "]\n", "svc_lin_acc = [\n", " 0.947,\n", " 0.667,\n", " 0.870,\n", " 0.734,\n", " 0.754,\n", " 0.701,\n", " 0.750,\n", " 0.639,\n", " 0.877,\n", " 0.767,\n", " 0.500,\n", " 0.475,\n", " 0.837,\n", " 0.768,\n", " 0.717,\n", " 0.050,\n", " 0.429,\n", "]\n", "svc_rbf_acc = [\n", " 0.947,\n", " 0.852,\n", " 0.500,\n", " 0.578,\n", " 0.772,\n", " 0.712,\n", " 0.821,\n", " 0.583,\n", " 0.912,\n", " 0.767,\n", " 0.500,\n", " 0.450,\n", " 0.837,\n", " 0.778,\n", " 0.441,\n", " 0.950,\n", " 0.339,\n", "]\n", "\n", "df = pd.DataFrame(\n", " {\n", " 'site': list(range(len(sites))) * 3,\n", " 'accuracy': rfc_acc + svc_lin_acc + svc_rbf_acc,\n", " 'Model': ['RFC'] * len(sites) + ['SVC_lin'] * len(sites) + ['SVC_rbf'] * len(sites),\n", " }\n", ")\n", "\n", "\n", "x = np.arange(len(sites))\n", "data = list(zip(rfc_acc, svc_lin_acc, svc_rbf_acc))\n", "\n", "dim = len(data[0])\n", "w = 0.81\n", "dimw = w / dim\n", "\n", "colors = ['dodgerblue', 'orange', 'darkorange']\n", "\n", "allvals = [rfc_acc, svc_lin_acc, svc_rbf_acc]\n", "\n", "fig = plt.figure(figsize=(10, 3))\n", "ax2 = plt.subplot2grid((1, 4), (0, 3))\n", "plot = sns.violinplot(\n", " data=df, x='Model', y='accuracy', ax=ax2, palette=colors, bw=0.1, linewidth=0.7\n", ")\n", "for i in range(dim):\n", " ax2.axhline(np.average(allvals[i]), ls='--', color=colors[i], lw=0.8)\n", "# ax2.axhline(np.percentile(allvals[i], 50), ls='--', color=colors[i], lw=.8)\n", "# sns.swarmplot(x=\"model\", y=\"accuracy\", data=df, color=\"w\", alpha=.5, ax=ax2);\n", "ax2.yaxis.tick_right()\n", "ax2.set_ylabel('')\n", "ax2.set_xticklabels(ax2.get_xticklabels(), rotation=40)\n", "ax2.set_ylim([0.0, 1.0])\n", "\n", "ax1 = plt.subplot2grid((1, 4), (0, 0), colspan=3)\n", "for i in range(dim):\n", " y = [d[i] for d in data]\n", " b = ax1.bar(x + i * dimw, y, dimw, bottom=0.001, color=colors[i], alpha=0.6)\n", " print(np.average(allvals[i]), np.std(allvals[i]))\n", " ax1.axhline(np.average(allvals[i]), ls='--', color=colors[i], lw=0.8)\n", "\n", "\n", "plt.xlim([-0.2, 16.75])\n", "plt.grid(False)\n", "_ = plt.xticks(np.arange(0, 17) + 0.33, sites, rotation='vertical')\n", "ax1.set_ylim([0.0, 1.0])\n", "ax1.set_ylabel('Accuracy (ACC)')\n", "fig.savefig(op.join(outputs_path, 'figures/fig05-acc.pdf'), bbox_inches='tight', dpi=300)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "rfc_roc_auc = [\n", " 0.597,\n", " 0.380,\n", " 0.857,\n", " 0.610,\n", " 0.698,\n", " 0.692,\n", " 0.963,\n", " 0.898,\n", " 0.772,\n", " 0.596,\n", " 0.873,\n", " 0.729,\n", " 0.784,\n", " 0.860,\n", " 0.751,\n", " 0.900,\n", " 0.489,\n", "]\n", "svc_lin_roc_auc = [\n", " 0.583,\n", " 0.304,\n", " 0.943,\n", " 0.668,\n", " 0.691,\n", " 0.754,\n", " 1.000,\n", " 0.778,\n", " 0.847,\n", " 0.590,\n", " 0.857,\n", " 0.604,\n", " 0.604,\n", " 0.838,\n", " 0.447,\n", " 0.650,\n", " 0.501,\n", "]\n", "svc_rbf_roc_auc = [\n", " 0.681,\n", " 0.217,\n", " 0.827,\n", " 0.553,\n", " 0.738,\n", " 0.616,\n", " 0.889,\n", " 0.813,\n", " 0.845,\n", " 0.658,\n", " 0.779,\n", " 0.493,\n", " 0.726,\n", " 0.510,\n", " 0.544,\n", " 0.500,\n", " 0.447,\n", "]\n", "\n", "df = pd.DataFrame(\n", " {\n", " 'site': list(range(len(sites))) * 3,\n", " 'auc': rfc_roc_auc + svc_lin_roc_auc + svc_rbf_roc_auc,\n", " 'Model': ['RFC'] * len(sites) + ['SVC_lin'] * len(sites) + ['SVC_rbf'] * len(sites),\n", " }\n", ")\n", "\n", "x = np.arange(len(sites))\n", "data = list(zip(rfc_roc_auc, svc_lin_roc_auc, svc_rbf_roc_auc))\n", "\n", "dim = len(data[0])\n", "w = 0.81\n", "dimw = w / dim\n", "\n", "colors = ['dodgerblue', 'orange', 'darkorange']\n", "\n", "allvals = [rfc_roc_auc, svc_lin_roc_auc, svc_rbf_roc_auc]\n", "\n", "fig = plt.figure(figsize=(10, 3))\n", "ax2 = plt.subplot2grid((1, 4), (0, 3))\n", "plot = sns.violinplot(data=df, x='Model', y='auc', ax=ax2, palette=colors, bw=0.1, linewidth=0.7)\n", "for i in range(dim):\n", " ax2.axhline(np.average(allvals[i]), ls='--', color=colors[i], lw=0.8)\n", "\n", "ax2.yaxis.tick_right()\n", "ax2.set_ylabel('')\n", "ax2.set_xticklabels(ax2.get_xticklabels(), rotation=40)\n", "ax2.set_ylim([0.0, 1.0])\n", "\n", "ax1 = plt.subplot2grid((1, 4), (0, 0), colspan=3)\n", "for i in range(dim):\n", " y = [d[i] for d in data]\n", " b = ax1.bar(x + i * dimw, y, dimw, bottom=0.001, color=colors[i], alpha=0.6)\n", " print(np.average(allvals[i]), np.std(allvals[i]))\n", " ax1.axhline(np.average(allvals[i]), ls='--', color=colors[i], lw=0.8)\n", "\n", "\n", "plt.xlim([-0.2, 16.75])\n", "plt.grid(False)\n", "_ = plt.xticks(np.arange(0, 17) + 0.33, sites, rotation='vertical')\n", "ax1.set_ylim([0.0, 1.0])\n", "ax1.set_ylabel('Area under the curve (AUC)')\n", "fig.savefig(op.join(outputs_path, 'figures/fig05-auc.pdf'), bbox_inches='tight', dpi=300)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Evaluation on DS030\n", "\n", "This section deals with the results obtained on DS030.\n", "\n", "### Table 4: Confusion matrix" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "from sklearn.metrics import confusion_matrix\n", "\n", "pred_file = op.abspath(\n", " op.join(\n", " '..',\n", " 'mriqc/data/csv',\n", " 'mclf_run-20170724-191452_mod-rfc_ver-0.9.7-rc8_class-2_cv-loso_data-test_pred.csv',\n", " )\n", ")\n", "\n", "pred_y = pd.read_csv(pred_file)\n", "true_y = pd.read_csv(ds030_y_path)\n", "true_y.rater_1 *= -1\n", "true_y.rater_1[true_y.rater_1 < 0] = 0\n", "print(\n", " confusion_matrix(true_y.rater_1.tolist(), pred_y.pred_y.values.ravel().tolist(), labels=[0, 1])\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Figure 6A: Feature importances" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import seaborn as sns\n", "from sklearn.externals.joblib import load as loadpkl\n", "\n", "sns.set_style('white')\n", "\n", "# Get the RFC\n", "estimator = loadpkl(\n", " pkgrf(\n", " 'mriqc',\n", " 'data/mclf_run-20170724-191452_mod-rfc_ver-0.9.7-rc8_class-2_cv-loso_data-train_estimator.pklz',\n", " )\n", ")\n", "forest = estimator.named_steps['rfc']\n", "\n", "# Features selected in cross-validation\n", "features = [\n", " 'cjv',\n", " 'cnr',\n", " 'efc',\n", " 'fber',\n", " 'fwhm_avg',\n", " 'fwhm_x',\n", " 'fwhm_y',\n", " 'fwhm_z',\n", " 'icvs_csf',\n", " 'icvs_gm',\n", " 'icvs_wm',\n", " 'qi_1',\n", " 'qi_2',\n", " 'rpve_csf',\n", " 'rpve_gm',\n", " 'rpve_wm',\n", " 'snr_csf',\n", " 'snr_gm',\n", " 'snr_total',\n", " 'snr_wm',\n", " 'snrd_csf',\n", " 'snrd_gm',\n", " 'snrd_total',\n", " 'snrd_wm',\n", " 'summary_bg_k',\n", " 'summary_bg_stdv',\n", " 'summary_csf_k',\n", " 'summary_csf_mad',\n", " 'summary_csf_mean',\n", " 'summary_csf_median',\n", " 'summary_csf_p05',\n", " 'summary_csf_p95',\n", " 'summary_csf_stdv',\n", " 'summary_gm_k',\n", " 'summary_gm_mad',\n", " 'summary_gm_mean',\n", " 'summary_gm_median',\n", " 'summary_gm_p05',\n", " 'summary_gm_p95',\n", " 'summary_gm_stdv',\n", " 'summary_wm_k',\n", " 'summary_wm_mad',\n", " 'summary_wm_mean',\n", " 'summary_wm_median',\n", " 'summary_wm_p05',\n", " 'summary_wm_p95',\n", " 'summary_wm_stdv',\n", " 'tpm_overlap_csf',\n", " 'tpm_overlap_gm',\n", " 'tpm_overlap_wm',\n", "]\n", "\n", "nft = len(features)\n", "\n", "forest = estimator.named_steps['rfc']\n", "importances = np.median([tree.feature_importances_ for tree in forest.estimators_], axis=0)\n", "# importances = np.median(, axis=0)\n", "indices = np.argsort(importances)[::-1]\n", "\n", "df = {'Feature': [], 'Importance': []}\n", "for tree in forest.estimators_:\n", " for i in indices:\n", " df['Feature'] += [features[i]]\n", " df['Importance'] += [tree.feature_importances_[i]]\n", "fig = plt.figure(figsize=(20, 6))\n", "# plt.title(\"Feature importance plot\")\n", "sns.boxplot(x='Feature', y='Importance', data=pd.DataFrame(df), linewidth=1, notch=True)\n", "plt.xlabel(f'Features selected ({len(features)})')\n", "# plt.bar(range(nft), importances[indices],\n", "# color=\"r\", yerr=std[indices], align=\"center\")\n", "plt.xticks(range(nft))\n", "plt.gca().set_xticklabels([features[i] for i in indices], rotation=90)\n", "plt.xlim([-1, nft])\n", "plt.show()\n", "fig.savefig(\n", " op.join(outputs_path, 'figures', 'fig06-exp2-fi.pdf'),\n", " bbox_inches='tight',\n", " pad_inches=0,\n", " dpi=300,\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Figure 6B: Misclassified images of DS030" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "fn = [\n", " '10225',\n", " '10235',\n", " '10316',\n", " '10339',\n", " '10365',\n", " '10376',\n", " '10429',\n", " '10460',\n", " '10506',\n", " '10527',\n", " '10530',\n", " '10624',\n", " '10696',\n", " '10891',\n", " '10948',\n", " '10968',\n", " '10977',\n", " '11050',\n", " '11052',\n", " '11142',\n", " '11143',\n", " '11149',\n", " '50004',\n", " '50005',\n", " '50008',\n", " '50010',\n", " '50016',\n", " '50027',\n", " '50029',\n", " '50033',\n", " '50034',\n", " '50036',\n", " '50043',\n", " '50047',\n", " '50049',\n", " '50053',\n", " '50054',\n", " '50055',\n", " '50085',\n", " '60006',\n", " '60010',\n", " '60012',\n", " '60014',\n", " '60016',\n", " '60021',\n", " '60046',\n", " '60052',\n", " '60072',\n", " '60073',\n", " '60084',\n", " '60087',\n", " '70051',\n", " '70060',\n", " '70072',\n", "]\n", "fp = ['10280', '10455', '10523', '11112', '50020', '50048', '50052', '50061', '50073', '60077']" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "fn_clear = [('10316', 98), ('10968', 122), ('11050', 110), ('11149', 111)]" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "import nibabel as nb\n", "from mriqc.viz.utils import plot_slice\n", "\n", "for im, z in fn_clear:\n", " image_path = op.join(ds030_path, f'sub-{im}', 'anat', f'sub-{im}_T1w.nii.gz')\n", " imdata = nb.load(image_path).get_data()\n", "\n", " fig, ax = plt.subplots()\n", " plot_slice(imdata[..., z], annotate=True)\n", " fig.savefig(\n", " op.join(outputs_path, 'figures', f'fig-06_sub-{im}_slice-{z:03}.svg'),\n", " dpi=300,\n", " bbox_inches='tight',\n", " )\n", " plt.clf()\n", " plt.close()" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "fp_clear = [\n", " ('10455', 140),\n", " ('50073', 162),\n", "]\n", "for im, z in fp_clear:\n", " image_path = op.join(ds030_path, f'sub-{im}', 'anat', f'sub-{im}_T1w.nii.gz')\n", " imdata = nb.load(image_path).get_data()\n", "\n", " fig, ax = plt.subplots()\n", " plot_slice(imdata[..., z], annotate=True)\n", " fig.savefig(\n", " op.join(outputs_path, 'figures', f'fig-06_sub-{im}_slice-{z:03}.svg'),\n", " dpi=300,\n", " bbox_inches='tight',\n", " )\n", " plt.clf()\n", " plt.close()" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.1" } }, "nbformat": 4, "nbformat_minor": 1 } ================================================ FILE: docs/notebooks/SpikesPlotter.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "%matplotlib inline\n", "%load_ext autoreload\n", "%autoreload 2" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import os.path as op\n", "\n", "wf_path = '/home/oesteban/tmp/mriqc-newcircle/work/workflow_enumerator/funcMRIQC/'\n", "\n", "in_fft = op.join(\n", " wf_path,\n", " 'ComputeIQMs/_in_file_..home..oesteban..Data..example_artifacts_dataset..sub-ben01'\n", " '..func..sub-ben01_task-unknown_bold.nii.gz/SpikesFinderFFT/sub-ben01_task-unknown_bold_zsfft.nii.gz',\n", ")\n", "\n", "in_file = op.join(\n", " wf_path,\n", " '_in_file_..home..oesteban..Data..example_artifacts_dataset..sub-ben01..func..sub-ben01_'\n", " 'task-unknown_bold.nii.gz/reorient_and_discard/sub-ben01_task-unknown_bold.nii.gz',\n", ")\n", "in_spikes = op.join(\n", " wf_path,\n", " 'ComputeIQMs/_in_file_..home..oesteban..Data..example_artifacts_dataset..sub-ben01..func..'\n", " 'sub-ben01_task-unknown_bold.nii.gz/SpikesFinderFFT/sub-ben01_task-unknown_bold_spikes.tsv',\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "in_fft = op.join(\n", " wf_path,\n", " 'ComputeIQMs/_in_file_..home..oesteban..Data..circle-tests..sub-ds205s09'\n", " '..func..sub-ds205s09_task-view_acq-LR_run-02_bold.nii.gz/SpikesFinderFFT/sub-ds205s09_task-view_acq-LR_run-02_bold_zsfft.nii.gz',\n", ")\n", "\n", "in_file = op.join(\n", " wf_path,\n", " '_in_file_..home..oesteban..Data..circle-tests..sub-ds205s09..func..sub-ds205s09_'\n", " 'task-view_acq-LR_run-02_bold.nii.gz/reorient_and_discard/sub-ds205s09_task-view_acq-LR_run-02_bold.nii.gz',\n", ")\n", "in_spikes = op.join(\n", " wf_path,\n", " 'ComputeIQMs/_in_file_..home..oesteban..Data..circle-tests..sub-ds205s09'\n", " '..func..sub-ds205s09_task-view_acq-LR_run-02_bold.nii.gz/SpikesFinderFFT/sub-ds205s09_task-view_acq-LR_run-02_bold_spikes.tsv',\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from mriqc.viz import utils as mvu" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "\n", "spikes_list = [tuple(i) for i in np.atleast_2d(np.loadtxt(in_spikes, dtype=int)).tolist()]\n", "spikes_list += [(5, 14)]\n", "mvu.plot_spikes(in_file, in_fft, spikes_list)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "%matplotlib inline\n", "%load_ext autoreload\n", "%autoreload 2" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import os.path as op\n", "\n", "data_path = '/home/oesteban/Data/ABIDE/sub-50465/anat/sub-50465_T1w.nii.gz'\n", "data_path = '/home/oesteban/tmp/mriqc-newcircle/work/workflow_enumerator/funcMRIQC/_in_file_..home..oesteban..Data..example_artifacts_dataset..sub-ben04..func..sub-ben04_task-unknown_bold.nii.gz/compute_tsnr/stdev.nii.gz'\n", "# data_path = '/home/oesteban/Data/rewardBeastBIDS2/sub-119/func/sub-119_task-rest_sbref.nii.gz'\n", "# data_path ='/home/oesteban/tmp/mriqc-newcircle/work/workflow_enumerator/funcMRIQC/_in_file_..home..oesteban..Data..circle-tests..sub-ds003s03..func..sub-ds003s03_task-rhymejudgment_bold.nii.gz/compute_tsnr/stdev.nii.gz'" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from mriqc.viz import utils as mvu\n", "\n", "mvu.plot_mosaic(data_path, cmap='viridis')" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.5.2" } }, "nbformat": 4, "nbformat_minor": 1 } ================================================ FILE: docs/notebooks/Supplemental Materials.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Generation of supplemental materials for the MRIQC paper\n", "\n", "This notebook is associated to the paper:\n", "\n", "Esteban O, Birman D, Schaer M, Koyejo OO, Poldrack RA, Gorgolewski KJ; MRIQC: Predicting Quality in Manual MRI Assessment Protocols Using No-Reference Image Quality Measures; bioRxiv 111294; doi:[10.1101/111294](https://doi.org/10.1101/111294)." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "%matplotlib inline\n", "%load_ext autoreload\n", "%autoreload 2\n", "import os.path as op\n", "\n", "import numpy as np\n", "import pandas as pd\n", "from mriqc.viz import misc as mviz\n", "from pkg_resources import resource_filename as pkgrf\n", "\n", "outputs_path = '../../mriqc-data/'" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "x_path = pkgrf('mriqc', 'data/csv/x_abide.csv')\n", "y_path = pkgrf('mriqc', 'data/csv/y_abide.csv')\n", "ds030_x_path = pkgrf('mriqc', 'data/csv/x_ds030.csv')\n", "ds030_y_path = pkgrf('mriqc', 'data/csv/y_ds030.csv')\n", "\n", "rater_types = {'rater_1': float, 'rater_2': float, 'rater_3': float}\n", "mdata = pd.read_csv(y_path, index_col=False, dtype=rater_types)\n", "\n", "sites = sorted(set(mdata.site.values.ravel().tolist()))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "fig = mviz.raters_variability_plot(\n", " mdata,\n", " raters=['rater_1', 'rater_2', 'rater_3'],\n", " rater_names=['Rater 1', 'Rater 2A', 'Rater 2B'],\n", " out_file=op.join(outputs_path, 'figures', 'suppl-fig02.pdf'),\n", " only_overlap=False,\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from sklearn.metrics import cohen_kappa_score\n", "\n", "overlap = mdata[np.all(~np.isnan(mdata[['rater_2', 'rater_3']]), axis=1)]\n", "y1 = overlap.rater_2.values.ravel().tolist()\n", "y2 = overlap.rater_3.values.ravel().tolist()\n", "\n", "fig = mviz.inter_rater_variability(\n", " y1,\n", " y2,\n", " raters=['Protocol A', 'Protocol B'],\n", " out_file=op.join(outputs_path, 'figures', 'suppl-intrarv.pdf'),\n", ")\n", "\n", "print(f\"Cohen's Kappa {cohen_kappa_score(y1, y2):f}\")\n", "\n", "y1 = overlap.rater_2.values.ravel()\n", "y1[y1 == 0] = 1\n", "\n", "y2 = overlap.rater_3.values.ravel()\n", "y2[y2 == 0] = 1\n", "print(f\"Cohen's Kappa (binarized): {cohen_kappa_score(y1, y2):f}\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "fig = mviz.plot_corrmat(x_path)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "fig = mviz.plot_histograms(x_path, y_path)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.1" } }, "nbformat": 4, "nbformat_minor": 2 } ================================================ FILE: docs/notebooks/finding_spikes.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "import nibabel\n", "import nilearn\n", "import nipy\n", "import numpy as np\n", "import pylab as plt\n", "import seaborn as sns\n", "from nilearn.image import mean_img, new_img_like\n", "from nilearn.masking import compute_epi_mask\n", "from nilearn.plotting import plot_anat, plot_epi, plot_roi\n", "from nipy.labs.mask import compute_mask\n", "\n", "%matplotlib inline" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "compute_mask?" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "in_file = 'data/sub-ben01_task-unknown_bold.nii.gz'" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "in_4d_nii = nibabel.load(in_file)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "mean_nii = mean_img(in_4d_nii)\n", "plot_anat(mean_nii)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# mask_nii = compute_epi_mask(in_4d_nii)\n", "\n", "mask_data = compute_mask(mean_nii.get_data())\n", "mask_nii = new_img_like(mean_nii, mask_data)\n", "\n", "plot_roi(mask_nii, mean_nii)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "a = np.where(mask_nii.get_data() != 0)\n", "bbox = np.max(a[0]) - np.min(a[0]), np.max(a[1]) - np.min(a[1]), np.max(a[2]) - np.min(a[2])\n", "print(bbox)\n", "print(np.argmax(bbox))\n", "longest_axis = np.argmax(bbox)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "from scipy import ndimage\n", "\n", "# Input here is a binarized and intersected mask data from previous section\n", "dil_mask = ndimage.binary_dilation(\n", " mask_nii.get_data(), iterations=int(mask_nii.shape[longest_axis] / 8)\n", ")\n", "\n", "# Now, we visualize the same using `plot_roi` with data being converted to Nifti\n", "# image. In all new image like, reference image is the same but second argument\n", "# varies with data specific\n", "dil_mask_nii = new_img_like(mask_nii, dil_mask.astype(np.int))\n", "plot_roi(dil_mask_nii, mean_nii)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "dil_mask.shape" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "rep = list(mask_nii.shape)\n", "rep[longest_axis] = -1\n", "new_mask_2d = dil_mask.max(axis=longest_axis).reshape(rep)\n", "new_mask_2d.shape" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "rep = [1, 1, 1]\n", "rep[longest_axis] = mask_nii.shape[longest_axis]\n", "new_mask_3d = np.logical_not(np.tile(new_mask_2d, rep))\n", "# new_mask_3d =" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "new_mask_nii = new_img_like(mask_nii, new_mask_3d.astype(np.int))\n", "plot_roi(new_mask_nii, mean_nii)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "from scipy.stats.mstats import zscore\n", "\n", "data4d = in_4d_nii.get_data()\n", "for slice_i in range(in_4d_nii.shape[2]):\n", " slice_data = data4d[:, :, slice_i, :][new_mask_3d[:, :, slice_i]].mean(axis=0)\n", " slice_data = zscore(slice_data)\n", " plt.plot(slice_data)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "data4d[:, :, slice_i, :][new_mask_3d[:, :, slice_i]].shape" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "data4d.shape" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "compute_mask?" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "def plot_spikes(in_file, skip=0):\n", " in_4d_nii = nibabel.load(in_file)\n", " mean_nii = mean_img(in_4d_nii)\n", " plot_anat(mean_nii)\n", " mask_data = compute_mask(mean_nii.get_data())\n", " mask_nii = new_img_like(mean_nii, mask_data)\n", "\n", " plot_roi(mask_nii, mean_nii)\n", "\n", " a = np.where(mask_nii.get_data() != 0)\n", " bbox = np.max(a[0]) - np.min(a[0]), np.max(a[1]) - np.min(a[1]), np.max(a[2]) - np.min(a[2])\n", " print(bbox)\n", " print(np.argmax(bbox))\n", " longest_axis = np.argmax(bbox)\n", "\n", " from scipy import ndimage\n", "\n", " # Input here is a binarized and intersected mask data from previous section\n", " dil_mask = ndimage.binary_dilation(\n", " mask_nii.get_data(), iterations=int(mask_nii.shape[longest_axis] / 8)\n", " )\n", "\n", " # Now, we visualize the same using `plot_roi` with data being converted to Nifti\n", " # image. In all new image like, reference image is the same but second argument\n", " # varies with data specific\n", " dil_mask_nii = new_img_like(mask_nii, dil_mask.astype(np.int))\n", " plot_roi(dil_mask_nii, mean_nii)\n", "\n", " rep = list(mask_nii.shape)\n", " rep[longest_axis] = -1\n", " new_mask_2d = dil_mask.max(axis=longest_axis).reshape(rep)\n", " new_mask_2d.shape\n", "\n", " rep = [1, 1, 1]\n", " rep[longest_axis] = mask_nii.shape[longest_axis]\n", " new_mask_3d = np.logical_not(np.tile(new_mask_2d, rep))\n", "\n", " new_mask_nii = new_img_like(mask_nii, new_mask_3d.astype(np.int))\n", " plot_roi(new_mask_nii, mean_nii)\n", "\n", " from scipy.stats.mstats import zscore\n", "\n", " data4d = in_4d_nii.get_data()[:, :, :, skip:]\n", " plt.figure()\n", " for slice_i in range(in_4d_nii.shape[2]):\n", " slice_data = data4d[:, :, slice_i, :][new_mask_3d[:, :, slice_i]].mean(axis=0)\n", " # slice_data = zscore(slice_data)\n", " plt.plot(slice_data)\n", " plt.title(in_file)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "plot_spikes('D:/example_artifacts_dataset/sub-ben01/func/sub-ben01_task-unknown_bold.nii.gz')" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "from glob import glob" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "scrolled": false }, "outputs": [], "source": [ "for file in glob('D:/*/sub-*/func/sub-*_task-*_bold.nii.gz'):\n", " plot_spikes(file)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "plot_anat?" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "glob('D:/*/sub-*/func/sub-*_task-*_bold.nii.gz')" ] } ], "metadata": { "anaconda-cloud": {}, "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.5.2" } }, "nbformat": 4, "nbformat_minor": 1 } ================================================ FILE: docs/source/_static/bold-1subject-1task.html ================================================

Start: 2017-03-24 07:49:23

Finish: 2017-03-24 08:31:04

Duration: 41.68 minutes

Nodes: 33

Cores: 10

Estimated Resource

Actual Resource

Failed Node


07:49


07:59


08:09


08:19


08:29


08:39

Memory

Memory

Threads

Threads

================================================ FILE: docs/source/_static/bold-1subject-8tasks.html ================================================

Start: 2017-03-24 07:47:15

Finish: 2017-03-24 08:34:22

Duration: 47.11 minutes

Nodes: 264

Cores: 10

Estimated Resource

Actual Resource

Failed Node


07:47


07:57


08:07


08:17


08:27


08:37

Memory

Memory

Threads

Threads

================================================ FILE: docs/source/_static/example_anatreport.html ================================================ [File too large to display: 16.0 MB] ================================================ FILE: docs/source/_static/example_funcreport.html ================================================ sub-03286_ses-NOT1ACH001_task-rest_bold :: MRIQC's BOLD fMRI report

Basic echo-wise reports

Standard deviation of signal through time

The voxel-wise standard deviation of the signal (variability along time).

Get figure file: sub-03286/figures/sub-03286_ses-NOT1ACH001_task-rest_desc-stdev_bold.svg

View of the background of the voxel-wise average of the BOLD timeseries

This panel shows a mosaic enhancing the background around the head. Artifacts usually unveil themselves in the air surrounding the head, where no signal sources are present.

Get figure file: sub-03286/figures/sub-03286_ses-NOT1ACH001_task-rest_desc-background_bold.svg

Voxel-wise average of BOLD time-series, zoomed-in covering just the brain

This panel shows a mosaic of the brain. This mosaic is the most suitable to screen head-motion intensity inhomogeneities, global/local noise, signal leakage (for example, from the eyeballs and across the phase-encoding axis), etc.

Get figure file: sub-03286/figures/sub-03286_ses-NOT1ACH001_task-rest_desc-zoomed_bold.svg

Carpetplot and nuisance signals

The so-called «carpetplot» may assist in assessing head-motion derived artifacts and respiation effects.

Get figure file: sub-03286/figures/sub-03286_ses-NOT1ACH001_task-rest_desc-carpet_bold.svg

Extended echo-wise reports

Voxel-wise average of BOLD time-series

The average signal calculated across the last axis (time).

Get figure file: sub-03286/figures/sub-03286_ses-NOT1ACH001_task-rest_desc-mean_bold.svg

About

Errors

Reproducibility and provenance information

Thanks for using MRIQC. The following information may assist in reconstructing the provenance of the corresponding derivatives.

Rating widget

  • Exclude
  • Poor
  • Acceptable
  • Excellent

Comments

Rater confidence

  • Doubtful
  • Confident
Download
================================================ FILE: docs/source/about.rst ================================================ Introduction ************ .. include:: ../../README.rst :start-line: 3 ================================================ FILE: docs/source/changes.rst ================================================ What's new ********** .. include:: ../../CHANGES.rst ================================================ FILE: docs/source/conf.py ================================================ """ mriqc documentation build configuration file, created by sphinx-quickstart on Thu Feb 25 09:31:58 2016. This file is execfile()d with the current directory set to its containing dir. Note that not all possible configuration values are present in this autogenerated file. All configuration values have a default; values that are commented out serve to show the default. """ import os from packaging.version import Version from mriqc import __copyright__, __version__ # Disable etelemetry during doc builds os.environ['NIPYPE_NO_ET'] = '1' # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # -- General configuration ------------------------------------------------ # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.ifconfig', 'sphinx.ext.intersphinx', 'sphinx.ext.mathjax', 'sphinx.ext.napoleon', 'sphinx.ext.todo', 'sphinx.ext.viewcode', 'nipype.sphinxext.plot_workflow', 'sphinxarg.ext', # argparse extension # 'sphinx.ext.autosectionlabel', ] # Mock modules in autodoc: autodoc_mock_imports = [ 'dipy', 'matplotlib', 'nilearn', 'numpy', 'pandas', 'scipy', 'seaborn', 'sklearn', 'statsmodels', 'xgboost', ] suppress_warnings = ['image.nonlocal_uri'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # source_suffix = ['.rst', '.md'] source_suffix = '.rst' # The encoding of source files. # source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = 'mriqc' author = 'The NiPreps Developers' copyright = __copyright__ # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The full version, including alpha/beta/rc tags. release = __version__ # The short X.Y version. version = Version(__version__).public # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = 'en' # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # today = '' # Else, today_fmt is used as the format for a strftime call. # today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = [] # The reST default role (used for this markup: `text`) to use for all # documents. # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. # keep_warnings = False # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = True # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'sphinx_rtd_theme' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". # html_title = None # A shorter title for the navigation bar. Default is the same as html_title. # html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. # html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. # html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. # html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. # html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. # html_additional_pages = {} # If false, no module index is generated. # html_domain_indices = True # If false, no index is generated. html_use_index = True # If true, the index is split into individual pages for each letter. # html_split_index = False # If true, links to the reST sources are added to the pages. # html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = None # Language to be used for generating the HTML full-text search index. # Sphinx supports the following languages: # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' # html_search_language = 'en' # A dictionary with options for the search language support, empty by default. # Now only 'ja' uses this config value # html_search_options = {'type': 'default'} # The name of a javascript file (relative to the configuration directory) that # implements a search results scorer. If empty, the default will be used. # html_search_scorer = 'scorer.js' # Output file base name for HTML help builder. htmlhelp_basename = 'mriqcdoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # 'preamble': '', # Latex figure (float) alignment # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ ( master_doc, 'mriqc.tex', 'mriqc Documentation', author, 'manual', ), ] # The name of an image file (relative to this directory) to place at the top of # the title page. # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # latex_use_parts = False # If true, show page references after internal links. # latex_show_pagerefs = False # If true, show URL addresses after external links. # latex_show_urls = False # Documents to append as an appendix to all manuals. # latex_appendices = [] # If false, no module index is generated. # latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [(master_doc, 'mriqc', 'mriqc Documentation', [author], 1)] # If true, show URL addresses after external links. # man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ( master_doc, 'mriqc', 'mriqc Documentation', author, 'mriqc', 'One line description of project.', 'Miscellaneous', ), ] # Documents to append as an appendix to all manuals. # texinfo_appendices = [] # If false, no module index is generated. # texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. # texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. # texinfo_no_detailmenu = False # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = { 'bids': ('https://bids-standard.github.io/pybids/', None), 'matplotlib': ('https://matplotlib.org/', None), 'nibabel': ('https://nipy.org/nibabel/', None), 'nipype': ('https://nipype.readthedocs.io/en/latest/', None), 'numpy': ('https://numpy.org/doc/stable/', None), 'pandas': ('https://pandas.pydata.org/pandas-docs/dev/', None), 'python': ('https://docs.python.org/3/', None), 'scipy': ('https://docs.scipy.org/doc/scipy/reference', None), } ================================================ FILE: docs/source/dsa.rst ================================================ .. _dsa: Data Sharing Agreement ********************** Foundations ----------- (Text in this section has been retrieved from https://www.ihris.org/toolkit/tools/data-sharing.html) Data-sharing is an important way to increase the ability of researchers, scientists and policy-makers to analyze and translate data into meaningful reports and knowledge. Sharing data discourages duplication of effort in data collection and encourages diverse thinking, as others are able to use the data to answer questions that the initial data collectors may not have considered. Sharing data also encourages accountability and transparency, enabling researchers to validate one another's findings. Finally, data from multiple sources can often be combined to allow for comparisons that cross national and departmental lines. MRIQC DSA (Data Sharing Agreement) ---------------------------------- MRIQC extracts a vector of :abbr:`IQMs (image quality metrics)` from the input dataset. These :abbr:`IQMs (image quality metrics)` are estimations of numerical properties of the data array in the image, hence they are not usable in identifying data. In other words, it is not possible to identify a natural person from this information. Additionally, MRIQC collects metadata from the appropriate fields found in the BIDS structure of the input dataset. Any information that could be used to identify the natural person the original data were obtained from are stripped out (e.g. the subject identifier in the context of the study, date of acquisition, phenotypical information, etc.). **MRIQC does not share the original input dataset**. Only an MD5 summary (*checksum*) of the input data is calculated and can be used to trace back the original dataset which MRIQC extracted the information from. This checksum cannot be used to regenerate the original data it was calculated from. Therefore, in the case that MRIQC was run on private data, the original images remain inaccessible to the public as no original data are shared. Withdrawing records and period of agreement ........................................... If you do not agree to these terms, please make use of the ``--no-sub`` (*do not submit*) command line flag with MRIQC. MRIQC will not collect any data when you OPT-OUT with this command line flag. If you wish to withdraw :abbr:`IQMs (image quality metrics)` records from the database, please send a request to the NiPreps Developers indicating the MD5 checksums of the datasets that are to be removed. If any MD5 checksum matches any image publicly available or one or more associated quality rating records were found, then the corresponding record cannot be destroyed, withdrawn or recalled. Withdrawing can be exercised within twelve months of the submission of the IQMs to the Web-API. Quality ratings submitted via the "Rating Widget" of MRIQC reports or any other means cannot be withdrawn since it requires an active action from the submitter that makes them aware that the data will be automatically shared publicly. The MRIQC Web-API will keep collecting records until funding runs out. In the case the service is shut down, existing records will be posted in a permanent, static storage service. This agreement does not expire. Intended use of the data ........................ The resource is particularly designed for researchers to share image quality metrics and annotations that can readily be reused in training human experts and machine learning algorithms. The ultimate goal of the MRIQC Web-API is to allow the development of fully automated quality control tools that outperform expert ratings in identifying degenerate images. Constraints on use of the data .............................. Data are shared under a CC0 (public domain) license. Data confidentiality .................... Please use please make use of the ``--no-sub`` (*do not submit*) command line flag with MRIQC to ensure that your data remain confidential. Financial costs of data-sharing ............................... The MRIQC Web-API infrastructure is funded by NIMH grants R24MH117179, and ZICMH002960. ================================================ FILE: docs/source/index.rst ================================================ Welcome to *MRIQC*'s documentation! ################################### .. include:: ../../README.rst :start-line: 3 .. image :: _static/OHBM2017-poster.png Contents -------- .. toctree:: :maxdepth: 3 about install usage measures reports workflows dsa license changes Indices and tables ------------------ * :ref:`genindex` * :ref:`modindex` * :ref:`search` ================================================ FILE: docs/source/install.rst ================================================ Installing *MRIQC* ****************** *MRIQC* is a *NiPreps* (`www.nipreps.org `__) *BIDS App* [BIDSApps]_. As such, *MRIQC* can be installed manually (*Bare-metal installation*, see below) or containerized. For containerized execution with *Docker* or *Singularity*, please follow the documentation on the *NiPreps* site (`introduction `__). "Bare-metal" installation ------------------------- If, for some reason, you really need a custom installation, *MRIQC* can be installed as follows. First, please make sure you have the execution system dependencies installed (see below). Second, the latest development version of MRIQC can be installed from github using ``pip`` on a Python 3 environment:: python -m pip install -U mriqc .. warning:: As of MRIQC 0.9.4, Python 2 is no longer supported. .. warning:: MRIQC uses matplotlib to create graphics. By default, matplotlib is configured to plot through an interactive Tcl/tk interface, which requires a functional display to be available. In *head-less* settings (for example, when running under tmux), you may see an error:: _tkinter.TclError: couldn't connect to display "localhost:10.0" There are two pathways to fix this issue. One is setting up a virtual display with a tool like XVfb. Alternatively, you can configure your matplotlib distribution to perform on head-less mode by default. That is achieved by uncommenting the ``backend : Agg`` line in the matplotlib's configuration file. The location of the configuration file can be retrieved with Python:: >>> import matplotlib >>> print(matplotlib.matplotlib_fname()) Alternatively, you can issue the following shell command-line to edit this setting:: $ sed -i 's/\(backend *: \).*$/\1Agg/g' $( python -c "import matplotlib; print(matplotlib.matplotlib_fname())" ) Execution system dependencies ............................. If you are using a a `Neurodebian `_ Linux distribution, installation should be as easy as:: sudo apt-get install afni ants sudo ln -sf /usr/lib/ants/N4BiasFieldCorrection /usr/local/bin/ After installation, make sure that all the necessary binaries are added to the ``$PATH`` environment variable, for the user that will run ``mriqc``. Otherwise, you can follow each software installation guide: `AFNI `_, and `ANTs `_. .. warning:: Please note that *MRIQC* 22.0.0 and later requires Freesurfer's *SynthStrip* tool. Please also install *FreeSurfer* (above 7.2) using `their guidelines `__. If your *FreeSurfer* version is older than 7.2, then you can get *MRIQC*'s requirements with:: wget https://github.com/freesurfer/freesurfer/blob/dev/mri_synthstrip/synthstrip.1.pt -P ${FREESURFER_HOME}/models/ .. danger:: You will get the following error if you do not install *SynthStrip*'s requirement:: The 'model' trait of a _SynthStripInputSpec instance must be a pathlike object or string representing an existing file, but a value of '' was specified.` If the *SynthStrip* requirement was downloaded, please make sure your environment has defined the variable ``$FREESURFER_HOME`` and that it is pointing at the right directory:: echo $FREESURFER_HOME If the ``$FREESURFER_HOME`` environment variable is defined, check whether the model file is available at the expected path:: $ stat $FREESURFER_HOME/models/synthstrip.1.pt File: /opt/freesurfer/models/synthstrip.1.pt Size: 30851709 Blocks: 60264 IO Block: 4096 regular file Device: fd00h/64768d Inode: 12583379 Links: 1 Access: (0644/-rw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root) Access: 2024-04-09 11:13:29.091893354 +0200 Modify: 2023-01-20 09:39:54.284056264 +0100 Change: 2023-01-20 09:39:54.284056264 +0100 Birth: 2023-01-20 09:39:54.224056213 +0100 If *SynthStrip*'s model file is not present, the output will look like:: $ stat $FREESURFER_HOME/models/synthstrip.1.pt stat: cannot statx '/opt/freesurfer/models/synthstrip.1.pt': No such file or directory ================================================ FILE: docs/source/iqms/bold.rst ================================================ .. _iqms_bold: IQMs for functional images ========================== .. automodule:: mriqc.qc.functional :members: :undoc-members: :show-inheritance: ================================================ FILE: docs/source/iqms/dwi.rst ================================================ .. _iqms_dwi: IQMs for diffusion images ========================= .. automodule:: mriqc.qc.diffusion :members: :undoc-members: :show-inheritance: ================================================ FILE: docs/source/iqms/t1w.rst ================================================ .. _iqms_t1w: IQMs for structural images ========================== .. automodule:: mriqc.qc.anatomical :members: :undoc-members: :show-inheritance: ================================================ FILE: docs/source/license.rst ================================================ About the *NiPreps* framework licensing *************************************** Please check https://www.nipreps.org/community/licensing/ for detailed information on the criteria we use to license *MRIQC* and other projects of the framework. License information ------------------- Copyright (c) 2021, the *NiPreps* Developers. As of the 21.0.x pre-release and release series, *MRIQC* is licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Copyright (c) 2015-2021, the *MRIQC* developers and the CRN. All rights reserved. *MRIQC* 0.16.x series and earlier are licensed under the BSD 3-clause license. You may obtain a copy of the License at https://opensource.org/licenses/BSD-3-Clause All trademarks referenced herein are property of their respective holders. Notice ------ .. include:: ../../NOTICE ================================================ FILE: docs/source/measures.rst ================================================ .. _measures: Image Quality Metrics (IQMs) **************************** .. automodule:: mriqc.qc :members: :undoc-members: :show-inheritance: ================================================ FILE: docs/source/reports/bold.rst ================================================ .. _reports-bold: BOLD images =========== One individual report per input functional timeseries will be generated in the path ``/reports/sub-IDxxx_task-name_bold.html```. An example report is given `here <../_static/example_funcreport.html>`_. The individual report for the functional images is structured as follows: .. _reports-bold-summary: Summary ------- The first section summarizes some important information: * subject identifier, date and time of execution of ``mriqc``, software version; * workflow details and flags raised during execution; and * the extracted IQMs. .. _reports-bold-visual: Visual reports -------------- The section with visual reports contains: #. Mosaic view of the average BOLD signal. .. figure:: ../resources/reports-bold_mean.png :alt: mean epi mosaic #. Mosaic view of the temporal standard deviation. .. figure:: ../resources/reports-bold_sd.png :alt: sd of epi mosaic #. Summary plot, showing the slice-wise signal intensity at the extremes for the identification of spikes, the outliers metric, the DVARS and the :abbr:`FD (framewise displacement)`. Finally the so-called carpetplot [Power2016]_. The carpet plot rows correspond to voxelwise time series, and are separated into regions: cortical gray matter, deep gray matter, white matter and cerebrospinal fluid, cerebellum and the brain-edge or “crown” [Provins2022]_. The crown corresponds to the voxels located on a closed band around the brain [Patriat2015]_. .. figure:: ../resources/reports-bold_summary.png :alt: fMRI summary plot .. _reports-bold-verbose: Verbose reports --------------- If mriqc was run with the ``--verbose-reports`` flag, the following plots will be appended: #. Mosaic view of the average BOLD signal, zoomed-in to the bounding box of brain activation. .. figure:: ../resources/reports-bold_mean_zoom.png :alt: zoomed mean epi mosaic #. Mosaic view of the average BOLD signal, with background enhancement. .. figure:: ../resources/reports-bold_mean_bg.png :alt: mean epi background mosaic #. One rows of axial views at different Z-axis points showing the calculated brain mask. .. figure:: ../resources/reports-bold_mask.png :alt: bold brainmasks #. Mosaic view with animation for assessment of the co-registration to MNI space (roll over the image to activate the animation). .. figure:: ../resources/reports-bold_mni.png :alt: bold-mni coregistration .. _reports-bold-metadata: Metadata -------- If some metadata was found in the BIDS structure, it is reported here. .. topic:: References .. [Patriat2015] Patriat, R., EK Molloy, RM Birn, T. Guitchev, and A. Popov. “Using Edge Voxel Information to Improve Motion Regression for Rs-FMRI Connectivity Studies.” Brain Connectivity 5, no. 9 (28 2015): 582–95. doi: `10.1089/brain.2014.0321 `__. .. [Power2016] Power JD, A simple but useful way to assess fMRI scan qualities. NeuroImage. 2016. doi: `10.1016/j.neuroimage.2016.08.009 `__. .. [Provins2022] Provins, Céline, Christopher J. Markiewicz, Rastko Ciric, Mathias Goncalves, César Caballero-Gaudes, Russell Poldrack, Patric Hagmann, and Oscar Esteban. “Quality Control and Nuisance Regression of FMRI, Looking out Where Signal Should Not Be Found.” OSF Preprints, January 21, 2022. doi: `10.31219/osf.io/hz52v `__. ================================================ FILE: docs/source/reports/group.rst ================================================ .. _reports-group: Group reports ============= Once a sample has been processed with the appropriate :ref:`workflow `, all the :abbr:`IQMs (image quality metrics)` written out in JSON files are collected in a :abbr:`CSV (comma separated values)` table, at the standard path of ``/.csv``. Therefore, for structural images, the IQMs will be found in ``/T1w.csv``. Correspondingly, ``/bold.csv`` is the designated path for the functional IQM table. The group report will process the ``/.csv`` table to generate one strip-plot per IQM: .. figure:: ../resources/reports-group_overview.png :alt: appearance of the group reports The strip-plots are interactive, so each sample (the value of a specific IQM associated to an input image) can be clicked to open up the corresponding individual report. This is particularly useful when trying to identify subpar images that show themselves as outliers in the general distribution of the IQM: .. figure:: ../resources/reports-group_outlier.png :alt: clicking on an outlier In this example, the corresponding individual report for the selected sample will open up (`click here to see this example report `_). The boxplots shown are Tukey boxplots, with median and interquartile range shown. Whiskers extend to the farthest point that is within 1.5 * IQR of the upper (or lower) quartile. ================================================ FILE: docs/source/reports/smri.rst ================================================ .. _reports-smri: T1 and T2 -weighed images ========================= One individual report per input structural volume will be generated in the path ``/reports/sub-IDxxx_T1w.html```. An example report is given `here <../_static/example_anatreport.html>`_. The individual report for the structural images is structured as follows: .. _reports-smri-summary: Summary ------- The first section summarizes some important information: * subject identifier, date and time of execution of ``mriqc``, software version; * workflow details and flags raised during execution; and * the extracted IQMs. .. _reports-smri-visual: Visual reports -------------- The section with visual reports contains: #. Mosaic view, zoomed-in over the parenchyma of the brain. .. figure:: ../resources/reports-t1w_mosaic_zoom.png :alt: zoomed in mosaic #. Mosaic view with background noise enhancement. .. figure:: ../resources/reports-t1w_background.png :alt: t1 background .. _reports-smri-verbose: Verbose reports --------------- If mriqc was run with the ``--verbose-reports`` flag, the following plots will be appended: #. Mosaic view with animation for assessment of the co-registration to MNI space (roll over the image to activate the animation). .. figure:: ../resources/reports-t1w_mni.svg :alt: t1-mni coregistration #. Three rows of axial views at different Z-axis points showing: the calculated brain mask, the segmentation done with FSL FAST and a saturated view of the background. .. figure:: ../resources/reports-t1w_masks1.png :alt: t1 verbose masks 1 #. Two rows of coronal views, showing the object/background segmentation, and the air/no-air segmentation. .. figure:: ../resources/reports-t1w_masks2.png :alt: t1 verbose masks 2 #. The :math:`\chi^2` function fitting for the calculation of the QI2: .. figure:: ../resources/reports-t1w_qi2.png :alt: t1 fitting of QI2 .. _reports-smri-metadata: Metadata -------- If some metadata was found in the BIDS structure, it is reported here. ================================================ FILE: docs/source/reports.rst ================================================ .. _reports: Visual Reports ************** Demo: anatomical reports ------------------------ .. raw:: html Demo: functional reports ------------------------ .. raw:: html How reports work ---------------- .. automodule:: mriqc.reports :members: :undoc-members: :show-inheritance: ================================================ FILE: docs/source/resources/mriqc.sbatch ================================================ #!/bin/bash # NOTE: To use this script, edit lines 17, 26, 40, 55, 56, 58 # labeled TODO with NOTES explaining how. # Set SBATCH Parameters: # ------------------------------------------ # NOTE: These should work with Slurm HPC systems, # but these specific parameters have only been tested on # Stanford's Sherlock. Some parameters may need to be # adjusted for other HPCs, specifically --partition. #SBATCH --job-name mriqc # NOTE: Each HCP has different partitions for job submissions. # Check your HCPs documentation for what partitions are available # to you. #SBATCH --partition normal #TODO: update for your HPC # NOTE: The --array parameter allows multiple jobs to be launched at once, # and is generally recommended to efficiently run several hundred jobs # simultaneously This should be adjusted to the range for your dataset; # 1-n%j where n is the number of # participants and j is the maximum number of concurrent jobs. # In this example, there are 216 participants, and only 50 run # at a given time. #SBATCH --array=1-216%50 #TODO: adjust for your dataset # NOTE: These parameters request time and resources from the job # scheduler. These specific parameters should be sufficient for # most datasets. #SBATCH --time=1:00:00 #NOTE: likely longer than generally needed #SBATCH --ntasks 1 #SBATCH --cpus-per-task=16 #SBATCH --mem-per-cpu=4G # NOTE: These parameters set where log files will be written, and # where status emails will be sent. #SBATCH --output log/%x-%A-%a.out #SBATCH --error log/%x-%A-%a.err #SBATCH --mail-user=%u@stanford.edu #TODO: update to your email domain #SBATCH --mail-type=ALL ## More information about these Slurm commands can be found at: # https://slurm.schedmd.com/sbatch.html # ------------------------------------------ # Setup variables # ------------------------------------------ # NOTE: These variables are paths to your data, and where you'd # like your output to be written. You should replace STUDY # with the directory one level below the directory where your data # is stored. # You should replace `ds0002785` with the name of your BIDS directory STUDY="/scratch/users/mphagen/mriqc-protocol" #TODO: replace with your path BIDS_DIR="${STUDY}/ds002785" # TODO: replace with path to your dataset # NOTE: These variables set the "Apptainer" execution for your # MRIQC container MRIQC_VERSION="24.0.2" #TODO: update if using a different version APPTAINER_CMD="apptainer run -e mriqc_${MRIQC_VERSION}.sif" OUTPUT_DIR="${BIDS_DIR}/derivatives/mriqc-${MRIQC_VERSION}" # NOTE: The next two variables are used to extract participant IDs from # the mandatory participants.tsv. SLURM_ARRAY_TASK_ID is generated by the #--array parameter, and is determined by each job's order - i.e. the # first job has the SLURM_ARRAY_TASK_ID=1, the second SLURM_ARRAY_TASK_ID=2. # It is necessary to add 1 to the SLURM_ARRAY_TASK_ID because of the header # in participants.tsv. subject_idx=$(( ${SLURM_ARRAY_TASK_ID} + 1 )) ## NOTE: The first clause in this line selects a row in participants.tsv # using subject_idx. This is piped to grep to isolate the subject id. # This regex should work for most subject naming conventions, but # may need to be modified in some cases. subject=$( sed -n ${subject_idx}p ${BIDS_DIR}/participants.tsv \ | grep -oP "sub-[A-Za-z0-9_]*" ) echo Subject $subject # Define the Apptainer command that will be ran, with MRIQC CLI flags cmd="${APPTAINER_CMD} ${BIDS_DIR} ${OUTPUT_DIR} participant \ --participant-label $subject \ -w $PWD/work/ \ --omp-nthreads 10 --nprocs 12" # For nodes with at least 32GB RAM # Print useful information to log files echo Running task ${SLURM_ARRAY_TASK_ID} echo Commandline: $cmd # Run the full command defined in cmd eval $cmd exitcode=$? # Print useful information to log files echo "sub-$subject ${SLURM_ARRAY_TASK_ID} $exitcode" \ >> ${SLURM_ARRAY_JOB_ID}.tsv echo Finished tasks ${SLURM_ARRAY_TASK_ID} with exit code $exitcode exit $exitcode ================================================ FILE: docs/source/resources/sbatch.sh ================================================ #!/bin/bash # #SBATCH -J mriqc #SBATCH --array=1-13 #SBATCH --time=136:00:00 #SBATCH -n 1 #SBATCH --cpus-per-task=16 #SBATCH --mem-per-cpu=4G # Outputs ---------------------------------- #SBATCH -o log/%x-%A-%a.out #SBATCH -e log/%x-%A-%a.err #SBATCH --mail-user=%u@gmail.com #SBATCH --mail-type=ALL # ------------------------------------------ unset PYTHONPATH BIDS_DIR="$STUDY/ds000030" OUTPUT_DIR="${BIDS_DIR}/derivatives/mriqc-0.16.1" SINGULARITY_CMD="singularity run -e $STUDY/mriqc-0.16.1.simg" subject=$( sed -n -E \ "$((${SLURM_ARRAY_TASK_ID} + 1))s/sub (\S*)\>.*/\1/gp" \ ${BIDS_DIR}/participants.tsv ) cmd="${SINGULARITY_CMD} ${BIDS_DIR} ${OUTPUT_DIR} participant \ --participant-label $subject \ -w $L_SCRATCH/work/ \ --omp-nthreads 8 --mem 10" echo Running task ${SLURM_ARRAY_TASK_ID} echo Commandline: $cmd eval $cmd exitcode=$? echo "sub-$subject ${SLURM_ARRAY_TASK_ID} $exitcode" \ >> ${SLURM_ARRAY_JOB_ID}.tsv echo Finished tasks ${SLURM_ARRAY_TASK_ID} with exit code $exitcode exit $exitcode ================================================ FILE: docs/source/usage.rst ================================================ .. _running_mriqc: Running *MRIQC* *************** *MRIQC* is a `BIDS-App `_ [BIDSApps]_, and therefore it inherently understands the :abbr:`BIDS (brain imaging data structure)` standard [BIDS]_. Before moving forward, please make sure to have read and understood *NiPreps*'s `introductory documentation `__). Containerized execution with *Docker* and *Singularity*/*Apptainer* ------------------------------------------------------------------- For containerized execution with *Docker* or *Singularity*/*Apptainer*, please follow the documentation on the *NiPreps* site, which contains tip and troubleshooting guidelines for both `Docker `__, and `Singularity or Apptainer `__. In addition to container-specific guidelines, the documentation also includes specific `help for processing DataLad-managed datasets `__. The rest of this documentation page applies to both *bare-metal* and containerized execution modes. *MRIQC* can fetch data in *DataLad* datasets -------------------------------------------- As of version 22.0.3, *MRIQC* bundles *DataLad*, enabling automatic data fetching in *DataLad* datasets. Employing this feature in containerized environments may lead to somewhat obscure errors (see, for example, `nipreps/mriqc#1307 `__). If you intend to use *DataLad* datasets, please read carefully *NiPreps*' `help for processing DataLad-managed datasets `__. Alternatively, this feature can be disabled by adding ``--no-datalad-get`` to the command line. This will separate *DataLad* management from *MRIQC*'s operation, which can be an effective way of debugging issues and averting erroneous conditions. Troubleshooting --------------- If you encounter problems, please check our `NiPreps Guidelines for Singularity or Apptainer `__. Common tips and guidelines that used to be found within *MRIQC*'s or *fMRIPrep*'s documentation sites have been relocated in the general *NiPreps* website. A *BIDS Apps* command line interface ------------------------------------ *MRIQC* follows the *BIDS Apps* standard command line interface:: mriqc bids-root/ output-folder/ participant That simple command runs *MRIQC* on all the *T1w* and *BOLD* images found under the BIDS-compliant folder ``bids-root/``. The last ``participant`` keyword indicates that the first level analysis is run. (i.e. extracting the :abbr:`IQMs (image quality metrics)` from the images retrieved within ``bids-root/``). The second level (``group``) is automatically run if no particular subject is provided for analysis. .. note:: If the argument :code:`--participant-label` is not provided, then all subjects will be processed and the group level analysis will automatically be executed without need of running the command in item 3. To specify one particular subject, the ``--participant-label`` argument can be used:: mriqc bids-root/ output-folder/ participant --participant-label S01 S02 S03 That command will run MRIQC only on the subjects indicated: only ``bids-root/sub-S01``, ``bids-root/sub-S02``, and ``bids-root/sub-S03`` will be processed. In this case, the ``group`` level will not be triggered automatically. We generate the ``group`` level results (the group level report and the features CSV table) with: :: mriqc bids-root/ output-folder/ group Examples of the generated visual reports are found in :ref:`The MRIQC Reports `. .. warning:: MRIQC by default attempts to upload anonymized quality metrics to a publicly accessible web server (`mriqc.nimh.nih.gov `_). The uploaded data consists only of calculated quality metrics and scanning parameters. It removes all personal health information and participant identifiers. We try to collect this data to build normal distributions for improved outlier detection, but if you do not wish to participate you can disable the submission with the ``--no-sub`` flag. .. topic:: BIDS data organization The software automatically finds the data the input folder if it follows the :abbr:`BIDS (brain imaging data structure)` standard [BIDS]_. A fast and easy way to check that your dataset fulfills the :abbr:`BIDS (brain imaging data structure)` standard is the `BIDS validator `_. .. topic:: BIDS-App levels In the ``participant`` level, all individual images to be processed are run through the pipeline, and the :ref:`MRIQC measures ` are extracted and the individual reports (see :ref:`The MRIQC Reports `) generated. In the ``group`` level, the :abbr:`IQMs (image quality metrics)` extracted in first place are combined in a table and the group reports are generated. Command line interface ...................... .. argparse:: :ref: mriqc.cli.parser._build_parser :prog: mriqc :nodefault: :nodefaultconst: Running *MRIQC* on HPC with *Singularity*/*Apptainer* ----------------------------------------------------- We have profiled cores and memory usages with the *resource profiler* tool of *Nipype*. An *MRIQC* run of one subject (from the ABIDE) dataset, containing only one run, one BOLD task (resting-state) yielded the following report: .. raw:: html Using the ``MultiProc`` plugin of nipype with ``nprocs=10``, the workflow nodes run across the available processors for 41.68 minutes. A memory peak of 8GB is reached by the end of the runtime, when the plotting nodes are fired up. We also profiled MRIQC on a dataset with 8 tasks (one run per task), on ds030 of OpenfMRI: .. raw:: html Again, we used ``n_procs=10``. The software run for roughly about the same time (47.11 min). Most of the run time, memory usage keeps around a maximum of 10GB. Since we saw a memory consumption of 1-2GB during the the 1-task example, a rule of thumb may be that each task takes around 1GB of memory. .. topic:: References .. [BIDS] `Brain Imaging Data Structure `_ .. [BIDSApps] `BIDS-Apps: portable neuroimaging pipelines that understand BIDS datasets `_ ================================================ FILE: docs/source/workflows.rst ================================================ .. _workflows: Workflows ********* .. automodule:: mriqc.workflows :members: :undoc-members: :show-inheritance: ================================================ FILE: long_description.rst ================================================ MRIQC provides a series of image processing workflows to extract and compute a series of NR (no-reference), IQMs (image quality metrics) to be used in QAPs (quality assessment protocols) for MRI (magnetic resonance imaging). ================================================ FILE: mriqc/__init__.py ================================================ # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- # vi: set ft=python sts=4 ts=4 sw=4 et: # # Copyright 2021 The NiPreps Developers # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # We support and encourage derived works from this project, please read # about our expectations at # # https://www.nipreps.org/community/licensing/ # """ The mriqc package provides a series of :abbr:`NR (no-reference)`, :abbr:`IQMs (image quality metrics)` to used in :abbr:`QAPs (quality assessment protocols)` for :abbr:`MRI (magnetic resonance imaging)`. """ from mriqc._version import __version__ __copyright__ = 'Copyright 2016-2024, The NiPreps Developers' __download__ = f'https://github.com/nipreps/mriqc/archive/{__version__}.tar.gz' __all__ = ['__version__', '__copyright__', '__download__'] ================================================ FILE: mriqc/__main__.py ================================================ # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- # vi: set ft=python sts=4 ts=4 sw=4 et: # # Copyright 2022 The NiPreps Developers # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # We support and encourage derived works from this project, please read # about our expectations at # # https://www.nipreps.org/community/licensing/ # from .cli.run import main if __name__ == '__main__': import sys from . import __name__ as module # `python -m ` typically displays the command as __main__.py if '__main__.py' in sys.argv[0]: sys.argv[0] = f'{sys.executable} -m {module}' main() ================================================ FILE: mriqc/_warnings.py ================================================ # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- # vi: set ft=python sts=4 ts=4 sw=4 et: # # Copyright 2021 The NiPreps Developers # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # We support and encourage derived works from this project, please read # about our expectations at # # https://www.nipreps.org/community/licensing/ # """Manipulate Python warnings.""" import logging import sys from nipype import logging as nlogging logging.addLevelName(26, 'BANNER') # Add a new level for banners logging.addLevelName(25, 'IMPORTANT') # Add a new level between INFO and WARNING logging.addLevelName(15, 'VERBOSE') # Add a new level between INFO and DEBUG LOGGER_FMT = ( '%(asctime)s |{color} %(levelname)-8s {reset}|{color} %(name)-16s ' '{reset}|{color} %(message)s{reset}' ) DATE_FMT = '%Y-%m-%d %H:%M:%S' CONSOLE_COLORS = { logging.DEBUG: '\x1b[38;20m', logging.INFO: '\x1b[34;20m', 25: '\x1b[33;20m', logging.WARNING: '\x1b[93;20m', logging.ERROR: '\x1b[31;20m', logging.CRITICAL: '\x1b[31;1m', 'reset': '\x1b[0m', } class _LogFormatter(logging.Formatter): """Customize the log format.""" _colored = True def __init__(self, datefmt=None, colored=True, **kwargs): self._colored = colored super().__init__( datefmt=datefmt or DATE_FMT, fmt=LOGGER_FMT.format( color=CONSOLE_COLORS['reset'] if colored else '', reset=CONSOLE_COLORS['reset'] if colored else '', ), ) def format(self, record): reset = CONSOLE_COLORS['reset'] if self._colored else '' self._style._fmt = ( '%(message)s' if record.levelno == 26 else LOGGER_FMT.format( color=CONSOLE_COLORS.get( record.levelno, CONSOLE_COLORS['reset'], ) if self._colored else '', reset=reset, ) ) return super().format(record) nlogging.getLogger('nipype') _wlog = logging.getLogger('py.warnings') _numexprlog = logging.getLogger('numexpr.utils') _dataladlog = logging.getLogger('datalad') for logger_name in logging.root.manager.loggerDict: logging.getLogger(logger_name).handlers.clear() _root_logger = logging.getLogger() # _root_logger.handlers.clear() _handler = logging.StreamHandler(stream=sys.stdout) _handler.setFormatter(_LogFormatter()) _root_logger.addHandler(_handler) _wlog.addHandler(logging.NullHandler()) _numexprlog.addHandler(logging.NullHandler()) _dataladlog.addHandler(logging.NullHandler()) # def _warn(message, category=None, stacklevel=1, source=None): # """Redefine the warning function.""" # if category is not None: # category = type(category).__name__ # category = category.replace('type', 'WARNING') # logging.getLogger('py.warnings').debug(f"{category or 'WARNING'}: {message}") # def _showwarning(message, category, filename, lineno, file=None, line=None): # _warn(message, category=category) # warnings.warn = _warn # warnings.showwarning = _showwarning # warnings.filterwarnings("ignore", category=FutureWarning) # warnings.filterwarnings("ignore", category=DeprecationWarning) # warnings.filterwarnings("ignore", category=ResourceWarning) # # cmp is not used by mriqc, so ignore nipype-generated warnings # warnings.filterwarnings("ignore", "cmp not installed") # warnings.filterwarnings( # "ignore", "This has not been fully tested. Please report any failures." # ) # warnings.filterwarnings("ignore", "sklearn.externals.joblib is deprecated in 0.21") # warnings.filterwarnings("ignore", "can't resolve package from __spec__ or __package__") ================================================ FILE: mriqc/bin/__init__.py ================================================ # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- # vi: set ft=python sts=4 ts=4 sw=4 et: # # Copyright 2021 The NiPreps Developers # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # We support and encourage derived works from this project, please read # about our expectations at # # https://www.nipreps.org/community/licensing/ # """ ``mriqc``'s executables. """ ================================================ FILE: mriqc/bin/abide2bids.py ================================================ # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- # vi: set ft=python sts=4 ts=4 sw=4 et: # # Copyright 2021 The NiPreps Developers # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # We support and encourage derived works from this project, please read # about our expectations at # # https://www.nipreps.org/community/licensing/ # """ABIDE2BIDS download tool.""" from __future__ import annotations import errno import json import os import os.path as op import shutil import subprocess as sp import tempfile from argparse import ArgumentParser, RawTextHelpFormatter from multiprocessing import Pool import numpy as np from defusedxml import ElementTree as et from mriqc.bin import messages _curl_cmd = shutil.which('curl') _unzip_cmd = shutil.which('unzip') def main(): """Entry point.""" parser = ArgumentParser( description='ABIDE2BIDS downloader.', formatter_class=RawTextHelpFormatter, ) g_input = parser.add_argument_group('Inputs') g_input.add_argument('-i', '--input-abide-catalog', action='store', required=True) g_input.add_argument('-n', '--dataset-name', action='store', default='ABIDE Dataset') g_input.add_argument('-u', '--nitrc-user', action='store', default=os.getenv('NITRC_USER')) g_input.add_argument( '-p', '--nitrc-password', action='store', default=os.getenv('NITRC_PASSWORD'), ) g_outputs = parser.add_argument_group('Outputs') g_outputs.add_argument('-o', '--output-dir', action='store', default='ABIDE-BIDS') opts = parser.parse_args() if opts.nitrc_user is None or opts.nitrc_password is None: raise RuntimeError('NITRC user and password are required') dataset_desc = { 'BIDSVersion': '1.0.0rc3', 'License': 'CC Attribution-NonCommercial-ShareAlike 3.0 Unported', 'Name': opts.dataset_name, } out_dir = op.abspath(opts.output_dir) try: os.makedirs(out_dir) except OSError as exc: if exc.errno != errno.EEXIST: raise exc with open(op.join(out_dir, 'dataset_description.json'), 'w') as dfile: json.dump(dataset_desc, dfile) catalog = et.parse(opts.input_abide_catalog).getroot() urls = [el.get('URI') for el in catalog.iter() if el.get('URI') is not None] pool = Pool() args_list = [(url, opts.nitrc_user, opts.nitrc_password, out_dir) for url in urls] res = pool.map(fetch, args_list) tsv_data = np.array([('subject_id', 'site_name')] + res) np.savetxt( op.join(out_dir, 'participants.tsv'), tsv_data, fmt='%s', delimiter='\t', ) def fetch(args: (str, str, str, str)) -> (str, str): """ Downloads a subject and formats it into BIDS. Parameters ---------- args : Tuple[str, str, str, str] URL, NITRC user, NITRC password, destination Returns ------- Tuple[str, str] Subject ID, Site name """ out_dir = None if len(args) == 3: url, user, password = args else: url, user, password, out_dir = args tmpdir = tempfile.mkdtemp() if out_dir is None: out_dir = os.getcwd() else: out_dir = op.abspath(out_dir) pkg_id = [u[9:] for u in url.split('/') if u.startswith('NITRC_IR_')][0] sub_file = op.join(tmpdir, f'{pkg_id}.zip') cmd = [_curl_cmd, '-s', '-u', f'{user}:{password}', '-o', sub_file, url] sp.check_call(cmd) sp.check_call([_unzip_cmd, '-qq', '-d', tmpdir, '-u', sub_file]) abide_root = op.join(tmpdir, 'ABIDE') files = [] for root, path, fname in os.walk(abide_root): if fname and (fname[0].endswith('nii') or fname[0].endswith('nii.gz')): if path: root = op.join(root, path[0]) files.append(op.join(root, fname[0])) index = len(abide_root) + 1 site_name, sub_str = files[0][index:].split('/')[0].split('_') subject_id = 'sub-' + sub_str for i in files: ext = '.nii.gz' if i.endswith('.nii'): ext = '.nii' if 'mprage' in i: bids_dir = op.join(out_dir, subject_id, 'anat') try: os.makedirs(bids_dir) except OSError as exc: if exc.errno != errno.EEXIST: raise exc shutil.copy(i, op.join(bids_dir, subject_id + '_T1w' + ext)) if 'rest' in i: bids_dir = op.join(out_dir, subject_id, 'func') try: os.makedirs(bids_dir) except OSError as exc: if exc.errno != errno.EEXIST: raise exc shutil.copy(i, op.join(bids_dir, subject_id + '_rest_bold' + ext)) shutil.rmtree(tmpdir, ignore_errors=True, onerror=_myerror) success_message = messages.ABIDE_SUBJECT_FETCHED.format( subject_id=subject_id[4:], site_name=site_name ) print(success_message) return subject_id[4:], site_name def _myerror(message: str): """ Print warning in case an exception is raised for temporal files removal. Parameters ---------- message : str `shutil.rmtree()` error message """ warning = messages.ABIDE_TEMPORAL_WARNING.format(message=message) print(warning) if __name__ == '__main__': main() ================================================ FILE: mriqc/bin/dfcheck.py ================================================ # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- # vi: set ft=python sts=4 ts=4 sw=4 et: # # Copyright 2021 The NiPreps Developers # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # We support and encourage derived works from this project, please read # about our expectations at # # https://www.nipreps.org/community/licensing/ # """ Compares pandas dataframes by columns. """ import sys from argparse import ArgumentParser, RawTextHelpFormatter from pathlib import Path import numpy as np import pandas as pd from mriqc.bin import messages from mriqc.utils.misc import BIDS_COMP def read_iqms(feat_file): """Read in a features table.""" feat_file = Path(feat_file) if feat_file.suffix == '.csv': x_df = pd.read_csv(feat_file, index_col=False, dtype=dict.fromkeys(BIDS_COMP, str)) # Find present bids bits and sort by them bids_comps_present = list(set(x_df.columns) & set(BIDS_COMP)) bids_comps_present = [bit for bit in BIDS_COMP if bit in bids_comps_present] x_df = x_df.sort_values(by=bids_comps_present) # Remove sub- prefix in subject_id x_df.subject_id = x_df.subject_id.str.lstrip('sub-') # Remove columns that are not IQMs feat_names = x_df._get_numeric_data().columns.tolist() for col in BIDS_COMP: try: feat_names.remove(col) except ValueError: pass else: bids_comps_present = ['subject_id'] x_df = pd.read_csv(feat_file, index_col=False, sep='\t', dtype={'bids_name': str}) x_df = x_df.sort_values(by=['bids_name']) x_df['subject_id'] = x_df.bids_name.str.lstrip('sub-') x_df = x_df.drop(columns=['bids_name']) x_df.subject_id = ['_'.join(v.split('_')[:-1]) for v in x_df.subject_id.ravel()] feat_names = x_df._get_numeric_data().columns.tolist() for col in feat_names: if col.startswith(('size_', 'spacing_', 'Unnamed')): feat_names.remove(col) return x_df, feat_names, bids_comps_present def main(): """Entry point.""" parser = ArgumentParser( description='Compare two pandas dataframes.', formatter_class=RawTextHelpFormatter, ) g_input = parser.add_argument_group('Inputs') g_input.add_argument( '-i', '--input-csv', action='store', type=Path, required=True, help='input data frame', ) g_input.add_argument( '-r', '--reference-csv', action='store', type=Path, required=True, help='reference dataframe', ) g_input.add_argument( '--tolerance', type=float, default=1.0e-5, help='relative tolerance for comparison', ) opts = parser.parse_args() ref_df, ref_names, ref_bids = read_iqms(opts.reference_csv) tst_df, tst_names, tst_bids = read_iqms(opts.input_csv) ref_df.set_index(ref_bids) tst_df.set_index(tst_bids) if sorted(ref_bids) != sorted(tst_bids): sys.exit(messages.DFCHECK_DIFFERENT_BITS) if sorted(ref_names) != sorted(tst_names): sys.exit(messages.DFCHECK_CSV_COLUMNS) ref_df = ref_df.sort_values(by=ref_bids) tst_df = tst_df.sort_values(by=tst_bids) if len(ref_df) != len(tst_df): different_length_message = messages.DFCHECK_DIFFERENT_LENGTH.format( len_input=len(ref_df), len_reference=len(tst_df) ) print(different_length_message) tst_rows = tst_df[tst_bids] ref_rows = ref_df[ref_bids] print(tst_rows.shape, ref_rows.shape) tst_keep = np.sum(tst_rows.isin(ref_rows).values.ravel().tolist()) print(tst_keep) diff = ~np.isclose(ref_df[ref_names].values, tst_df[tst_names].values, rtol=opts.tolerance) if np.any(diff): # ne_stacked = pd.DataFrame(data=diff, columns=ref_names).stack() # ne_stacked = np.isclose(ref_df[ref_names], tst_df[ref_names]).stack() # changed = ne_stacked[ne_stacked] # changed.set_index(ref_bids) difference_locations = np.where(diff) changed_from = ref_df[ref_names].values[difference_locations] changed_to = tst_df[ref_names].values[difference_locations] cols = [ref_names[v] for v in difference_locations[1]] bids_df = ref_df.loc[difference_locations[0], ref_bids].reset_index() chng_df = pd.DataFrame({'iqm': cols, 'from': changed_from, 'to': changed_to}) table = pd.concat([bids_df, chng_df], axis=1) print(table[ref_bids + ['iqm', 'from', 'to']].to_string(index=False)) corr = pd.DataFrame() corr['iqms'] = ref_names corr['cc'] = [ float( np.corrcoef( ref_df[[var]].values.ravel(), tst_df[[var]].values.ravel(), rowvar=False, )[0, 1] ) for var in ref_names ] if np.any(corr.cc < 0.95): iqms = corr[corr.cc < 0.95] iqms_message = messages.DFCHECK_IQMS_UNDER_095.format(iqms=iqms) print(iqms_message) sys.exit(messages.DFCHECK_CSV_CHANGED) else: print(messages.DFCHECK_IQMS_CORRELATED) sys.exit(0) if __name__ == '__main__': main() ================================================ FILE: mriqc/bin/fs2gif.py ================================================ # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- # vi: set ft=python sts=4 ts=4 sw=4 et: # # Copyright 2021 The NiPreps Developers # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # We support and encourage derived works from this project, please read # about our expectations at # # https://www.nipreps.org/community/licensing/ # """ Batch export freesurfer results to animated gifs. """ import os import os.path as op import subprocess as sp from argparse import ArgumentParser, RawTextHelpFormatter from errno import EEXIST from shutil import rmtree, which from tempfile import mkdtemp import nibabel as nb import numpy as np from skimage import exposure def main(): """Entry point""" parser = ArgumentParser( description='Batch export freesurfer results to animated gifs.', formatter_class=RawTextHelpFormatter, ) g_input = parser.add_argument_group('Inputs') g_input.add_argument('-s', '--subject-id', action='store') g_input.add_argument('-t', '--temp-dir', action='store') g_input.add_argument('--keep-temp', action='store_true', default=False) g_input.add_argument('--zoom', action='store_true', default=False) g_input.add_argument('--hist-eq', action='store_true', default=False) g_input.add_argument('--use-xvfb', action='store_true', default=False) g_outputs = parser.add_argument_group('Outputs') g_outputs.add_argument('-o', '--output-dir', action='store', default='fs2gif') opts = parser.parse_args() if opts.temp_dir is None: tmpdir = mkdtemp() else: tmpdir = op.abspath(opts.temp_dir) try: os.makedirs(tmpdir) except OSError as exc: if exc.errno != EEXIST: raise exc out_dir = op.abspath(opts.output_dir) try: os.makedirs(out_dir) except OSError as exc: if exc.errno != EEXIST: raise exc subjects_dir = os.getenv('SUBJECTS_DIR', op.abspath('subjects')) subject_list = [opts.subject_id] if opts.subject_id is None: subject_list = [ op.basename(name) for name in os.listdir(subjects_dir) if op.isdir(os.path.join(subjects_dir, name)) ] environ = os.environ.copy() environ['SUBJECTS_DIR'] = subjects_dir if opts.use_xvfb: environ['doublebufferflag'] = 1 # tcl_file = pkgr.resource_filename('mriqc', 'data/fsexport.tcl') tcl_contents = """ SetOrientation 0 SetCursor 0 128 128 128 SetDisplayFlag 3 0 SetDisplayFlag 22 1 set i 0 """ for subid in subject_list: sub_path = op.join(subjects_dir, subid) tmp_sub = op.join(tmpdir, subid) try: os.makedirs(tmp_sub) except OSError as exc: if exc.errno != EEXIST: raise exc data = nb.load(op.join(sub_path, 'mri', 'norm.mgz')).get_fdata() data[data > 0] = 1 # Compute brain bounding box indexes = np.argwhere(data) bbox_min = indexes.min(0) bbox_max = indexes.max(0) + 1 center = np.average([bbox_min, bbox_max], axis=0) if opts.hist_eq: ref_file = op.join(tmp_sub, f'{subid}.mgz') img = nb.load(op.join(sub_path, 'mri', 'norm.mgz')) data = exposure.equalize_adapthist(img.get_fdata(), clip_limit=0.03) nb.MGHImage(data, img.affine, img.header).to_filename(ref_file) if not opts.zoom: # Export tiffs for left hemisphere tcl_file = op.join(tmp_sub, f'{subid}.tcl') with open(tcl_file, 'w') as tclfp: tclfp.write(tcl_contents) tclfp.write( f'for {{ set slice {bbox_min[2]} }} {{ $slice < {bbox_max[2]} }} {{ incr slice }} {{' ) tclfp.write(' SetSlice $slice\n') tclfp.write(' RedrawScreen\n') tclfp.write(f' SaveTIFF [format "{tmp_sub}/{subid}-%03d.tif" $i]\n') tclfp.write(' incr i\n') tclfp.write('}\n') tclfp.write('QuitMedit\n') cmd = [ 'tkmedit', subid, 'T1.mgz', 'lh.pial', '-aux-surface', 'rh.pial', '-tcl', tcl_file, ] if opts.use_xvfb: cmd = _xvfb_run() + cmd print('Running tkmedit: {}'.format(' '.join(cmd))) sp.call(cmd, env=environ) # Convert to animated gif print('Stacking coronal slices') sp.call( [ which('convert'), '-delay', '10', '-loop', '0', f'{tmp_sub}/{subid}-*.tif', f'{out_dir}/{subid}.gif', ] ) else: # Export tiffs for left hemisphere tcl_file = op.join(tmp_sub, f'lh-{subid}.tcl') with open(tcl_file, 'w') as tclfp: tclfp.write(tcl_contents) tclfp.write('SetZoomLevel 2') tclfp.write( 'for {{ set slice {bbox_min[2]} }} {{ $slice < {bbox_max[2]} }} {{ incr slice }} {{' ) tclfp.write(f' SetZoomCenter {center[0] + 30} {center[1] - 10} $slice\n') tclfp.write(' SetSlice $slice\n') tclfp.write(' RedrawScreen\n') tclfp.write(f' SaveTIFF [format "{tmp_sub}/{subid}-lh-%03d.tif" $i]\n') tclfp.write(' incr i\n') tclfp.write('}\n') tclfp.write('QuitMedit\n') cmd = ['tkmedit', subid, 'norm.mgz', 'lh.white', '-tcl', tcl_file] if opts.use_xvfb: cmd = _xvfb_run() + cmd print('Running tkmedit: {}'.format(' '.join(cmd))) sp.call(cmd, env=environ) # Convert to animated gif print('Stacking coronal slices') # Export tiffs for right hemisphere tcl_file = op.join(tmp_sub, f'rh-{subid}.tcl') with open(tcl_file, 'w') as tclfp: tclfp.write(tcl_contents) tclfp.write('SetZoomLevel 2') tclfp.write( 'for {{ set slice {bbox_min[2]} }} {{ $slice < {bbox_max[2]} }} {{ incr slice }} {{' ) tclfp.write(f' SetZoomCenter {center[0] - 30} {center[1] - 10} $slice\n') tclfp.write(' SetSlice $slice\n') tclfp.write(' RedrawScreen\n') tclfp.write(f' SaveTIFF [format "{tmp_sub}/{subid}-rh-%03d.tif" $slice]\n') tclfp.write(' incr i\n') tclfp.write('}\n') tclfp.write('QuitMedit\n') cmd = ['tkmedit', subid, 'norm.mgz', 'rh.white', '-tcl', tcl_file] if opts.use_xvfb: cmd = _xvfb_run() + cmd print('Running tkmedit: {}'.format(' '.join(cmd))) sp.call(cmd, env=environ) # Convert to animated gif print('Stacking coronal slices') sp.call( [ which('convert'), '-delay', '10', '-loop', '0', f'{tmp_sub}/{subid}-lh-*.tif', f'{out_dir}/{subid}-lh.gif', ] ) sp.call( [ which('convert'), '-delay', '10', '-loop', '0', f'{tmp_sub}/{subid}-rh-*.tif', f'{out_dir}/{subid}-rh.gif', ] ) if not opts.keep_temp: rmtree(tmp_sub, ignore_errors=True, onerror=_myerror) def _xvfb_run(wait=5, server_args='-screen 0, 1600x1200x24', logs=None): """ Wrap command with xvfb-run. Copied from: https://github.com/VUIIS/seam/blob/1dabd9ca5b1fc7d66ef7d41c34ea8d42d668a484/seam/util.py """ if logs is None: logs = op.join(mkdtemp(), 'fs2gif_xvfb') return [ 'xvfb-run', '-a', # automatically get a free server number f'-f {logs}.out', f'-e {logs}.err', f'--wait={wait:d}', f'--server-args="{server_args}"', ] def _myerror(msg): print(f'WARNING: Error deleting temporal files: {msg}') if __name__ == '__main__': main() ================================================ FILE: mriqc/bin/labeler.py ================================================ # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- # vi: set ft=python sts=4 ts=4 sw=4 et: # # Copyright 2021 The NiPreps Developers # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # We support and encourage derived works from this project, please read # about our expectations at # # https://www.nipreps.org/community/licensing/ # import csv import os import random import sys import webbrowser import numpy as np def num_rows(data): for j in range(1, 4): if len(data[j]) == 0: return j return 4 def main(): """read the input file""" print('Reading file sinfo.csv') csvfile = open('sinfo.csv', 'rb') csvreader = csv.reader(csvfile) file = list(csvreader) # display statistics finished = [0.0, 0.0, 0.0] hold = np.zeros((3, len(file) - 1)) hold[:] = np.nan total = 601 for i in range(1, len(file)): for j in range(1, 4): if len(file[i][j]) > 0: finished[j - 1] = finished[j - 1] + 1 hold[j - 1, i - 1] = int(file[i][j]) finished = np.divide(np.round(np.divide(finished, total) * 1000), 10) print(f'Completed: {" ".join([f"{f:g}%" for f in finished])}') print(f'Total: {np.round(np.divide(np.sum(finished), 3))}%') input('Waiting: [enter]') # file[1:] are all the rows order = range(1, len(file)) random.shuffle(order) # pick a random row for row in order: # check how many entries it has current = num_rows(file[row]) if current <= 1: # if less than 1, run the row print('Check participant #' + file[row][0]) fname = os.getcwd() + '/abide/' + file[row][0] if os.path.isfile(fname): webbrowser.open('file://' + fname) quality = input('Quality? [-1/0/1/e/c] ') if quality == 'e': break if quality == 'c': print('Current comment: ' + file[row][4]) comment = input('Comment: ') if len(comment) > 0: file[row][4] = comment quality = input('Quality? [-1/0/1/e] ') if quality == 'e': break file[row][current] = quality else: print('File does not exist') print('Writing file sinfo.csv') outfile = open('sinfo.csv', 'wb') csvwriter = csv.writer(outfile) csvwriter.writerows(file) print('Ending') if __name__ == '__main__': main() sys.exit(0) ================================================ FILE: mriqc/bin/messages.py ================================================ # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- # vi: set ft=python sts=4 ts=4 sw=4 et: # # Copyright 2021 The NiPreps Developers # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # We support and encourage derived works from this project, please read # about our expectations at # # https://www.nipreps.org/community/licensing/ # ABIDE_SUBJECT_FETCHED = 'Successfully processed subject {subject_id} from site {site_name}' ABIDE_TEMPORAL_WARNING = 'WARNING: Error deleting temporal files: {message}' BIDS_LABEL_MISSING = 'Participant label(s) not found in the BIDS root directory: {label}' BIDS_GROUP_SIZE = 'Group size should be at least 0 (i.e. all participants assigned to same group).' CLF_CAPTURED_WARNING = 'Captured warning ({category}): {message}' CLF_CLASSIFIER_MISSING = 'No training samples were given, and the --load-classifier option {info}.' CLF_SAVED_RESULTS = 'Results saved as {path}.' CLF_TRAIN_LOAD_ERROR = 'Errors ({n_errors}) loading training set: {errors}.' CLF_WRONG_PARAMETER_COUNT = 'Wrong number of parameters.' DFCHECK_CSV_CHANGED = 'Output CSV file changed one or more values.' DFCHECK_CSV_COLUMNS = 'Output CSV file changed number of columns.' DFCHECK_DIFFERENT_BITS = 'Dataset has different BIDS bits w.r.t. reference.' DFCHECK_DIFFERENT_LENGTH = """\ Input datasets have different lengths (input={len_input}, \ reference={len_reference}). """ DFCHECK_IQMS_CORRELATED = 'All IQMs show a Pearson correlation >= 0.95.' DFCHECK_IQMS_UNDER_095 = 'IQMs with Pearson correlation < 0.95:\n{iqms}' HASH_REPORT = '{sha} {file_name}' PLOT_REPORT_VERSION = 'mriqc version:\t{version}' PLOT_WORK_MISSING = 'Work directory of a previous MRIQC run was not found.' SUBJECT_WRANGLER_DESCRIPTION = """\ BIDS-Apps participants wrangler tool ------------------------------------ This command arranges the participant labels in groups for computation, and checks that the \ requested participants have the corresponding folder in the bids_dir.\ """ WEBAPI_GET = 'Sending GET to {address}.' WEBAPI_REPORT = 'There are {n_records} records in database.' ================================================ FILE: mriqc/bin/mriqcwebapi_test.py ================================================ # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- # vi: set ft=python sts=4 ts=4 sw=4 et: # # Copyright 2021 The NiPreps Developers # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # We support and encourage derived works from this project, please read # about our expectations at # # https://www.nipreps.org/community/licensing/ # from mriqc.bin import messages def get_parser(): """ Build parser object. """ from argparse import ArgumentParser, RawTextHelpFormatter parser = ArgumentParser( description='MRIQCWebAPI: Check entries.', formatter_class=RawTextHelpFormatter ) parser.add_argument( 'modality', action='store', choices=['T1w', 'bold'], help='number of expected items in the database', ) parser.add_argument( 'expected', action='store', type=int, help='number of expected items in the database', ) parser.add_argument( '--webapi-url', action='store', default='https://mriqc.nimh.nih.gov/api/v1/T1w', type=str, help='IP address where the MRIQC WebAPI is listening', ) return parser def main(): """Entry point.""" import logging from requests import get # Run parser MRIQC_LOG = logging.getLogger(__name__) opts = get_parser().parse_args() get_log_message = messages.WEBAPI_GET.format(address=opts.webapi_url) MRIQC_LOG.info(get_log_message) response = get(opts.webapi_url, timeout=5).json() n_records = response['_meta']['total'] response_log_message = messages.WEBAPI_REPORT.format(n_records=n_records) MRIQC_LOG.info(response_log_message) if opts.expected != n_records: raise AssertionError if __name__ == '__main__': main() ================================================ FILE: mriqc/bin/nib_hash.py ================================================ # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- # vi: set ft=python sts=4 ts=4 sw=4 et: # # Copyright 2021 The NiPreps Developers # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # We support and encourage derived works from this project, please read # about our expectations at # # https://www.nipreps.org/community/licensing/ # """ Extracts the sha hash of the contents of a nifti file. """ from argparse import ArgumentParser, RawTextHelpFormatter from hashlib import sha1 import nibabel as nb from mriqc.bin import messages def get_parser() -> ArgumentParser: """ A trivial parser. Returns ------- ArgumentParser nib_hash execution parser """ parser = ArgumentParser( description='Compare two pandas dataframes.', formatter_class=RawTextHelpFormatter, ) parser.add_argument('input_file', action='store', help='input nifti file') return parser def get_hash(nii_file: str) -> str: """ Computes the sha1 hash for a given NIfTI format file. Parameters ---------- nii_file : str Path to *nii* file Returns ------- str SHA1 hash """ with nb.openers.ImageOpener(nii_file) as fobj: return sha1(fobj.read()).hexdigest() def main(): """Entry point.""" file_name = get_parser().parse_args().input_file sha = get_hash(file_name) message = messages.HASH_REPORT.format(sha=sha, file_name=file_name) print(message) if __name__ == '__main__': main() ================================================ FILE: mriqc/bin/subject_wrangler.py ================================================ # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- # vi: set ft=python sts=4 ts=4 sw=4 et: # # Copyright 2021 The NiPreps Developers # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # We support and encourage derived works from this project, please read # about our expectations at # # https://www.nipreps.org/community/licensing/ # """BIDS-Apps subject wrangler.""" import glob import os.path as op from argparse import ArgumentParser, RawTextHelpFormatter from random import shuffle from textwrap import dedent from mriqc import __version__ from mriqc.bin import messages COMMAND = '{exec} {bids_dir} {out_dir} participant --participant_label {labels} {work_dir} {arguments} {logfile}' # noqa: E501 def main(): """Entry point.""" parser = ArgumentParser( formatter_class=RawTextHelpFormatter, description=dedent(messages.SUBJECT_WRANGLER_DESCRIPTION), ) parser.add_argument( '-v', '--version', action='version', version=f'mriqc v{__version__}', ) parser.add_argument( 'bids_dir', action='store', help='The directory with the input dataset formatted according to the BIDS standard.', ) parser.add_argument( 'output_dir', action='store', help='The directory where the output files ' 'should be stored. If you are running group level analysis ' 'this folder should be prepopulated with the results of the' 'participant level analysis.', ) parser.add_argument( '--participant_label', '--subject_list', '-S', action='store', help='The label(s) of the participant(s) that should be analyzed. ' 'The label corresponds to sub- from the ' 'BIDS spec (so it does not include "sub-"). If this parameter ' 'is not provided all subjects should be analyzed. Multiple ' 'participants can be specified with a space separated list.', nargs='*', ) parser.add_argument( '--group-size', default=1, action='store', type=int, help='Parallelize participants in groups.', ) parser.add_argument( '--no-randomize', default=False, action='store_true', help='Do not randomize participants list before grouping.', ) parser.add_argument( '--log-groups', default=False, action='store_true', help='Append logging output.', ) parser.add_argument( '--multiple-workdir', default=False, action='store_true', help='Split work directories by jobs.', ) parser.add_argument( '--bids-app-name', default='mriqc', action='store', help='BIDS app to call.', ) parser.add_argument('--args', default='', action='store', help='Append arguments.') opts = parser.parse_args() # Build settings dict bids_dir = op.abspath(opts.bids_dir) subject_dirs = glob.glob(op.join(bids_dir, 'sub-*')) all_subjects = sorted([op.basename(subj)[4:] for subj in subject_dirs]) subject_list = opts.participant_label if subject_list is None or not subject_list: subject_list = all_subjects else: # remove sub- prefix, get unique for i, subj in enumerate(subject_list): subject_list[i] = subj[4:] if subj.startswith('sub-') else subj subject_list = sorted(set(subject_list)) if list(set(subject_list) - set(all_subjects)): non_exist = list(set(subject_list) - set(all_subjects)) missing_label_error = messages.BIDS_LABEL_MISSING.format(label=' '.join(non_exist)) raise RuntimeError(missing_label_error) if not opts.no_randomize: shuffle(subject_list) gsize = opts.group_size if gsize < 0: raise RuntimeError(messages.BIDS_GROUP_SIZE) if gsize == 0: gsize = len(subject_list) j = i + gsize groups = [subject_list[i:j] for i in range(0, len(subject_list), gsize)] log_arg = '>> log/mriqc-{:04d}.log' if opts.log_groups else '' workdir_arg = ' -w work/sjob-{:04d}' if opts.multiple_workdir else '' for i, part_group in enumerate(groups): kwargs = { 'exec': opts.bids_app_name, 'bids_dir': bids_dir, 'out_dir': opts.output_dir, 'labels': ' '.join(part_group), 'work_dir': workdir_arg.format(i), 'arguments': opts.args, 'logfile': log_arg.format(i), } print(COMMAND.format(**kwargs)) if __name__ == '__main__': main() ================================================ FILE: mriqc/cli/__init__.py ================================================ ================================================ FILE: mriqc/cli/parser.py ================================================ # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- # vi: set ft=python sts=4 ts=4 sw=4 et: # # Copyright 2021 The NiPreps Developers # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # We support and encourage derived works from this project, please read # about our expectations at # # https://www.nipreps.org/community/licensing/ # """Parser.""" import re from mriqc import config def _parse_participant_labels(value): """ Drop ``sub-`` prefix of participant labels. >>> _parse_participant_labels("s060") ['s060'] >>> _parse_participant_labels("sub-s060") ['s060'] >>> _parse_participant_labels("s060 sub-s050") ['s050', 's060'] >>> _parse_participant_labels("s060 sub-s060") ['s060'] >>> _parse_participant_labels("s060\tsub-s060") ['s060'] """ return sorted( {re.sub(r'^sub-', '', item.strip()) for item in re.split(r'\s+', f'{value}'.strip())} ) def _build_parser(): """Build parser object.""" import sys import warnings from argparse import Action, ArgumentDefaultsHelpFormatter, ArgumentParser from functools import partial from pathlib import Path from packaging.version import Version from .version import check_latest, is_flagged class DeprecateAction(Action): def __call__(self, parser, namespace, values, option_string=None): warnings.warn( f'Argument {option_string} is deprecated and is *ignored*.', stacklevel=2, ) delattr(namespace, self.dest) class ParticipantLabelAction(Action): def __call__(self, parser, namespace, values, option_string=None): setattr(namespace, self.dest, _parse_participant_labels(' '.join(values))) def _path_exists(path, parser): """Ensure a given path exists.""" if path is None or not Path(path).exists(): raise parser.error(f'Path does not exist: <{path}>.') return Path(path).expanduser().absolute() def _min_one(value, parser): """Ensure an argument is not lower than 1.""" value = int(value) if value < 1: raise parser.error("Argument can't be less than one.") return value def _to_gb(value): scale = {'G': 1, 'T': 10**3, 'M': 1e-3, 'K': 1e-6, 'B': 1e-9} digits = ''.join([c for c in value if c.isdigit()]) n_digits = len(digits) units = value[n_digits:] or 'G' return int(digits) * scale[units[0]] def _bids_filter(value): from json import loads if value and Path(value).exists(): return loads(Path(value).read_text()) verstr = f'MRIQC v{config.environment.version}' currentv = Version(config.environment.version) parser = ArgumentParser( description=f"""\ MRIQC {config.environment.version} Automated Quality Control and visual reports for Quality Assessment of structural \ (T1w, T2w) and functional MRI of the brain. {config.DSA_MESSAGE}""", formatter_class=ArgumentDefaultsHelpFormatter, ) PathExists = partial(_path_exists, parser=parser) PositiveInt = partial(_min_one, parser=parser) # Arguments as specified by BIDS-Apps # required, positional arguments # IMPORTANT: they must go directly with the parser object parser.add_argument( 'bids_dir', action='store', type=PathExists, help='The root folder of a BIDS valid dataset (sub-XXXXX folders should ' 'be found at the top level in this folder).', ) parser.add_argument( 'output_dir', action='store', type=Path, help='The directory where the output files ' 'should be stored. If you are running group level analysis ' 'this folder should be prepopulated with the results of the ' 'participant level analysis.', ) parser.add_argument( 'analysis_level', action='store', nargs='+', help='Level of the analysis that will be performed. ' 'Multiple participant level analyses can be run independently ' '(in parallel) using the same output_dir.', choices=['participant', 'group'], ) # optional arguments parser.add_argument('--version', action='version', version=verstr) parser.add_argument( '-v', '--verbose', dest='verbose_count', action='count', default=0, help='Increases log verbosity for each occurrence, debug level is -vvv.', ) # TODO: add 'mouse', 'macaque', and other populations once the pipeline is working parser.add_argument( '--species', action='store', type=str, default='human', choices=['human', 'rat'], help='Use appropriate template for population', ) g_bids = parser.add_argument_group('Options for filtering BIDS queries') g_bids.add_argument( '--participant-label', '--participant_label', '--participant-labels', '--participant_labels', dest='participant_label', action=ParticipantLabelAction, nargs='+', help='A space delimited list of participant identifiers or a single ' 'identifier (the sub- prefix can be removed).', ) g_bids.add_argument( '--bids-filter-file', action='store', type=Path, metavar='PATH', help='a JSON file describing custom BIDS input filter using pybids ' '{:{:,...},...} ' '(https://github.com/bids-standard/pybids/blob/master/src/bids/layout/config/bids.json)', ) g_bids.add_argument( '--session-id', action='store', nargs='*', type=str, help='Filter input dataset by session ID.', ) g_bids.add_argument( '--run-id', action='store', type=int, nargs='*', help='DEPRECATED - This argument will be disabled. Use ``--bids-filter-file`` instead.', ) g_bids.add_argument( '--task-id', action='store', nargs='*', type=str, help='Filter input dataset by task ID.', ) g_bids.add_argument( '-m', '--modalities', action='store', choices=config.SUPPORTED_SUFFIXES, default=config.SUPPORTED_SUFFIXES, nargs='*', help='Filter input dataset by MRI type.', ) g_bids.add_argument('--dsname', type=str, help='A dataset name.') g_bids.add_argument( '--bids-database-dir', metavar='PATH', help='Path to an existing PyBIDS database folder, for faster indexing ' '(especially useful for large datasets).', ) g_bids.add_argument( '--bids-database-wipe', action='store_true', default=False, help='Wipe out previously existing BIDS indexing caches, forcing re-indexing.', ) g_bids.add_argument( '--no-datalad-get', action='store_false', dest='datalad_get', help='Disable attempting to get remote files in DataLad datasets.', ) # General performance g_perfm = parser.add_argument_group('Options to handle performance') g_perfm.add_argument( '--nprocs', '--n_procs', '--n_cpus', '-n-cpus', action='store', type=PositiveInt, help="""\ Maximum number of simultaneously running parallel processes executed by *MRIQC* \ (e.g., several instances of ANTs' registration). \ However, when ``--nprocs`` is greater or equal to the ``--omp-nthreads`` option, \ it also sets the maximum number of threads that simultaneously running processes \ may aggregate (meaning, with ``--nprocs 16 --omp-nthreads 8`` a maximum of two \ 8-CPU-threaded processes will be running at a given time). \ Under this mode of operation, ``--nprocs`` sets the maximum number of processors \ that can be assigned work within an *MRIQC* job, which includes all the processors \ used by currently running single- and multi-threaded processes. \ If ``None``, the number of CPUs available will be automatically assigned (which may \ not be what you want in, e.g., shared systems like a HPC cluster.""", ) g_perfm.add_argument( '--omp-nthreads', '--ants-nthreads', action='store', type=PositiveInt, help="""\ Maximum number of threads that multi-threaded processes executed by *MRIQC* \ (e.g., ANTs' registration) can use. \ If ``None``, the number of CPUs available will be automatically assigned (which may \ not be what you want in, e.g., shared systems like a HPC cluster.""", ) g_perfm.add_argument( '--mem', '--mem_gb', '--mem-gb', dest='memory_gb', action='store', type=_to_gb, help='Upper bound memory limit for MRIQC processes.', ) g_perfm.add_argument( '--testing', dest='debug', action='store_true', default=False, help='Use testing settings for a minimal footprint.', ) g_perfm.add_argument( '-f', '--float32', action='store_true', default=True, help="Cast the input data to float32 if it's represented in higher precision " '(saves space and improves performance).', ) g_perfm.add_argument( '--pdb', dest='pdb', action='store_true', default=False, help='Open Python debugger (pdb) on exceptions.', ) # Control instruments g_outputs = parser.add_argument_group('Instrumental options') g_outputs.add_argument( '-w', '--work-dir', action='store', type=Path, default=Path('work').absolute(), help='Path where intermediate results should be stored.', ) g_outputs.add_argument('--verbose-reports', default=False, action='store_true') g_outputs.add_argument('--reports-only', default=False, action='store_true') g_outputs.add_argument( '--write-graph', action='store_true', default=False, help='Write workflow graph.', ) g_outputs.add_argument( '--dry-run', action='store_true', default=False, help='Do not run the workflow.', ) g_outputs.add_argument( '--resource-monitor', '--profile', dest='resource_monitor', action='store_true', default=False, help='Hook up the resource profiler callback to nipype.', ) g_outputs.add_argument( '--use-plugin', action='store', default=None, type=Path, help='Nipype plugin configuration file.', ) g_outputs.add_argument( '--crashfile-format', action='store', default='txt', choices=['txt', 'pklz'], type=str, help='Nipype crashfile format', ) g_outputs.add_argument( '--no-sub', default=False, action='store_true', help="Turn off submission of anonymized quality metrics to MRIQC's metrics repository.", ) g_outputs.add_argument( '--email', action='store', default='', type=str, help='Email address to include with quality metric submission.', ) g_outputs.add_argument( '--webapi-url', action='store', type=str, help='IP address where the MRIQC WebAPI is listening.', ) g_outputs.add_argument( '--webapi-port', action='store', type=int, help='Port where the MRIQC WebAPI is listening.', ) g_outputs.add_argument( '--upload-strict', action='store_true', default=False, help='Upload will fail if upload is strict.', ) g_outputs.add_argument( '--notrack', action='store_true', help='Opt-out of sending tracking information of this run to the NiPreps developers. This' ' information helps to improve MRIQC and provides an indicator of real world usage ' ' crucial for obtaining funding.', ) # ANTs options g_ants = parser.add_argument_group('Specific settings for ANTs') g_ants.add_argument( '--ants-float', action='store_true', default=False, help='Use float number precision on ANTs computations.', ) g_ants.add_argument( '--ants-settings', action='store', help='Path to JSON file with settings for ANTs.', ) # Diffusion workflow settings g_dwi = parser.add_argument_group('Diffusion MRI workflow configuration') g_dwi.add_argument( '--min-dwi-length', action='store', default=config.workflow.min_len_dwi, dest='min_len_dwi', help='Drop DWI runs with fewer orientations than this threshold.', type=int, ) # Functional workflow settings g_func = parser.add_argument_group('Functional MRI workflow configuration') g_func.add_argument( '--min-bold-length', action='store', default=config.workflow.min_len_bold, dest='min_len_bold', help='Drop BOLD runs with fewer time points than this threshold.', type=int, ) g_func.add_argument( '--fft-spikes-detector', action='store_true', default=False, help='Turn on FFT based spike detector (slow).', ) g_func.add_argument( '--fd_thres', action='store', default=0.2, type=float, help='Threshold on framewise displacement estimates to detect outliers.', ) g_func.add_argument( '--deoblique', action='store_true', default=False, help='Deoblique the functional scans during head motion correction preprocessing.', ) g_func.add_argument( '--despike', action='store_true', default=False, help='Despike the functional scans during head motion correction preprocessing.', ) g_func.add_argument( '--start-idx', action=DeprecateAction, type=int, help='DEPRECATED Initial volume in functional timeseries that should be ' 'considered for preprocessing.', ) g_func.add_argument( '--stop-idx', action=DeprecateAction, type=int, help='DEPRECATED Final volume in functional timeseries that should be ' 'considered for preprocessing.', ) latest = check_latest() if latest is not None and currentv < latest: print( f"""\ You are using MRIQC v{currentv}, and a newer version is available: {latest}.""", file=sys.stderr, ) _blist = is_flagged() if _blist[0]: _reason = _blist[1] or 'unknown' print( f"""\ WARNING: This version of MRIQC ({config.environment.version}) has been FLAGGED (reason: {_reason}). That means some severe flaw was found in it and we strongly \ discourage its usage.""", file=sys.stderr, ) return parser def parse_args(args=None, namespace=None): """Parse args and run further checks on the command line.""" from json import loads from logging import DEBUG, FileHandler from pathlib import Path from pprint import pformat from niworkflows.utils.bids import DEFAULT_BIDS_QUERIES, collect_data from mriqc import __version__, data from mriqc._warnings import DATE_FMT, LOGGER_FMT, _LogFormatter from mriqc.messages import PARTICIPANT_START from mriqc.utils.misc import initialize_meta_and_data parser = _build_parser() opts = parser.parse_args(args, namespace) config.execution.log_level = int(max(25 - 5 * opts.verbose_count, DEBUG)) config.loggers.init() _log_file = Path(opts.output_dir) / 'logs' / f'mriqc-{config.execution.run_uuid}.log' _log_file.parent.mkdir(exist_ok=True, parents=True) _handler = FileHandler(_log_file) _handler.setFormatter( _LogFormatter( fmt=LOGGER_FMT.format(color='', reset=''), datefmt=DATE_FMT, colored=False, ) ) config.loggers.default.addHandler(_handler) extra_messages = [''] if opts.bids_filter_file: extra_messages.insert( 0, f' * BIDS filters-file: {opts.bids_filter_file.absolute()}.', ) notice_path = data.load.readable('NOTICE') config.loggers.cli.log( 26, PARTICIPANT_START.format( version=__version__, bids_dir=opts.bids_dir, output_dir=opts.output_dir, analysis_level=opts.analysis_level, notice='\n '.join( ['NOTICE'] + notice_path.read_text().splitlines(keepends=False)[1:] ), extra_messages='\n'.join(extra_messages), ), ) config.from_dict(vars(opts)) # Load base plugin_settings from file if --use-plugin if opts.use_plugin is not None: from yaml import safe_load as loadyml with open(opts.use_plugin) as f: plugin_settings = loadyml(f) _plugin = plugin_settings.get('plugin') if _plugin: config.nipype.plugin = _plugin config.nipype.plugin_args = plugin_settings.get('plugin_args', {}) config.nipype.nprocs = config.nipype.plugin_args.get('nprocs', config.nipype.nprocs) # Load BIDS filters if opts.bids_filter_file: config.execution.bids_filters = { k.lower(): v for k, v in loads(opts.bids_filter_file.read_text()).items() } bids_dir = config.execution.bids_dir output_dir = config.execution.output_dir work_dir = config.execution.work_dir version = config.environment.version # Ensure input and output folders are not the same if output_dir == bids_dir: parser.error( 'The selected output folder is the same as the input BIDS folder. ' f'Please modify the output path (suggestion: {bids_dir}).' / 'derivatives' / ('mriqc-{}'.format(version.split('+')[0])) ) if bids_dir in work_dir.parents: parser.error( 'The selected working directory is a subdirectory of the input BIDS folder. ' 'Please modify the output path.' ) config.execution.bids_dir_datalad = ( config.execution.datalad_get and (bids_dir / '.git').exists() and (bids_dir / '.datalad').exists() ) # Setup directories config.execution.log_dir = output_dir / 'logs' # Check and create output and working directories config.execution.log_dir.mkdir(exist_ok=True, parents=True) output_dir.mkdir(exist_ok=True, parents=True) work_dir.mkdir(exist_ok=True, parents=True) # Force initialization of the BIDSLayout config.execution.init() participant_label = [ d.name[4:] for d in config.execution.bids_dir.glob('sub-*') if d.is_dir() and d.exists() ] if config.execution.participant_label is not None: selected_label = set(config.execution.participant_label) if missing_subjects := selected_label - set(participant_label): parser.error( 'One or more participant labels were not found in the BIDS directory: ' f'{", ".join(missing_subjects)}.' ) participant_label = selected_label config.execution.participant_label = sorted(participant_label) # Handle analysis_level analysis_level = set(config.workflow.analysis_level) if not config.execution.participant_label: analysis_level.add('group') config.workflow.analysis_level = list(analysis_level) # List of files to be run lc_modalities = [mod.lower() for mod in config.execution.modalities] bids_dataset, _ = collect_data( config.execution.layout, config.execution.participant_label, session_id=config.execution.session_id, task=config.execution.task_id, group_echos=True, bids_filters={mod: config.execution.bids_filters.get(mod, {}) for mod in lc_modalities}, queries={mod: DEFAULT_BIDS_QUERIES[mod] for mod in lc_modalities}, ) # Drop empty queries bids_dataset = {mod: files for mod, files in bids_dataset.items() if files} config.workflow.inputs = bids_dataset # Check the query is not empty if not list(config.workflow.inputs.values()): ffile = ( '(--bids-filter-file was not set)' if not opts.bids_filter_file else f"(with '--bids-filter-file {opts.bids_filter_file}')" ) parser.error( f"""\ Querying BIDS dataset at <{config.execution.bids_dir}> got an empty result. Please, check out your currently set filters {ffile}: {pformat(config.execution.bids_filters, indent=2, width=99)}""" ) # Check no DWI or others are sneaked into MRIQC unknown_mods = set(config.workflow.inputs.keys()) - { suffix.lower() for suffix in config.SUPPORTED_SUFFIXES } if unknown_mods: parser.error( f'MRIQC is unable to process the following modalities: {", ".join(unknown_mods)}.' ) initialize_meta_and_data() # set specifics for alternative populations if opts.species.lower() != 'human': config.workflow.species = opts.species # TODO: add other species once rats are working if opts.species.lower() == 'rat': config.workflow.template_id = 'Fischer344' # mean distance from the lateral edge to the center of the brain is # ~ PA:10 mm, LR:7.5 mm, and IS:5 mm (see DOI: 10.1089/089771503770802853) # roll movement is most likely to occur, so set to 7.5 mm config.workflow.fd_radius = 7.5 # block uploads for the moment; can be reversed before wider release config.execution.no_sub = True ================================================ FILE: mriqc/cli/run.py ================================================ # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- # vi: set ft=python sts=4 ts=4 sw=4 et: # # Copyright 2021 The NiPreps Developers # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # We support and encourage derived works from this project, please read # about our expectations at # # https://www.nipreps.org/community/licensing/ # """Definition of the command line interface's (CLI) entry point.""" def format_elapsed_time(elapsed_timedelta): """Format a timedelta instance as a %Hh %Mmin %Ss string.""" return ( f'{elapsed_timedelta.days * 24 + elapsed_timedelta.seconds // 3600:02d}h ' f'{(elapsed_timedelta.seconds % 3600) // 60:02d}min ' f'{elapsed_timedelta.seconds % 60:02d}s' ) def main(argv=None): """Entry point for MRIQC's CLI.""" import atexit import datetime import gc import os import sys import time from tempfile import mkstemp from mriqc import config, messages from mriqc.cli.parser import parse_args atexit.register(config.restore_env) config.settings.start_time = time.time() # Run parser parse_args(argv) if config.execution.pdb: from mriqc.utils.debug import setup_exceptionhook setup_exceptionhook() config.nipype.plugin = 'Linear' # CRITICAL Save the config to a file. This is necessary because the execution graph # is built as a separate process to keep the memory footprint low. The most # straightforward way to communicate with the child process is via the filesystem. # The config file name needs to be unique, otherwise multiple mriqc instances # will create write conflicts. config_file = config.to_filename() config.loggers.cli.info(f'MRIQC config file: {config_file}.') exitcode = 0 # Set up participant level if 'participant' in config.workflow.analysis_level: _pool = None if config.nipype.plugin in ('MultiProc', 'LegacyMultiProc'): import multiprocessing as mp import multiprocessing.forkserver from concurrent.futures import ProcessPoolExecutor from contextlib import suppress os.environ['OMP_NUM_THREADS'] = '1' os.environ['NUMEXPR_MAX_THREADS'] = '1' with suppress(RuntimeError): mp.set_start_method('fork') gc.collect() _pool = ProcessPoolExecutor( max_workers=config.nipype.nprocs, initializer=config._process_initializer, initargs=(config_file,), ) _resmon = None if config.execution.resource_monitor: from mriqc.instrumentation.resources import ResourceRecorder _resmon = ResourceRecorder( pid=os.getpid(), log_file=mkstemp( dir=config.execution.work_dir, prefix='.resources.', suffix='.tsv' )[1], ) _resmon.start() if not config.execution.notrack: from ..utils.telemetry import setup_migas setup_migas() # CRITICAL Call build_workflow(config_file, retval) in a subprocess. # Because Python on Linux does not ever free virtual memory (VM), running the # workflow construction jailed within a process preempts excessive VM buildup. from multiprocessing import Manager, Process with Manager() as mgr: from .workflow import build_workflow retval = mgr.dict() p = Process(target=build_workflow, args=(str(config_file), retval)) p.start() p.join() mriqc_wf = retval.get('workflow', None) exitcode = p.exitcode or retval.get('return_code', 0) # CRITICAL Load the config from the file. This is necessary because the ``build_workflow`` # function executed constrained in a process may change the config (and thus the global # state of MRIQC). config.load(config_file) exitcode = exitcode or (mriqc_wf is None) * os.EX_SOFTWARE if exitcode != 0: sys.exit(exitcode) # Initialize nipype config config.nipype.init() # Make sure loggers are started config.loggers.init() if _resmon: config.loggers.cli.info(f'Started resource recording at {_resmon._logfile}.') # Resource management options if config.nipype.plugin in ('MultiProc', 'LegacyMultiProc') and ( 1 < config.nipype.nprocs < config.nipype.omp_nthreads ): config.loggers.cli.warning( 'Per-process threads (--omp-nthreads=%d) exceed total ' 'threads (--nthreads/--n_cpus=%d)', config.nipype.omp_nthreads, config.nipype.nprocs, ) # Check synthstrip is properly installed if not config.environment.synthstrip_path: config.loggers.cli.warning( ( 'Please make sure FreeSurfer is installed and the FREESURFER_HOME ' 'environment variable is defined and pointing at the right directory.' ) if config.environment.freesurfer_home is None else ( f'FreeSurfer seems to be installed at {config.environment.freesurfer_home},' " however SynthStrip's model is not found at the expected path." ) ) if mriqc_wf is None: sys.exit(os.EX_SOFTWARE) if mriqc_wf and config.execution.write_graph: mriqc_wf.write_graph(graph2use='colored', format='svg', simple_form=True) if not config.execution.dry_run and not config.execution.reports_only: # Warn about submitting measures BEFORE if not config.execution.no_sub: config.loggers.cli.warning(config.DSA_MESSAGE) # Clean up master process before running workflow, which may create forks gc.collect() # run MRIQC _plugin = config.nipype.get_plugin() if _pool: from mriqc.engine.plugin import MultiProcPlugin _plugin = { 'plugin': MultiProcPlugin(pool=_pool, plugin_args=config.nipype.plugin_args), } mriqc_wf.run(**_plugin) # Warn about submitting measures AFTER if not config.execution.no_sub: config.loggers.cli.warning(config.DSA_MESSAGE) if not config.execution.dry_run: from mriqc.reports.individual import generate_reports generate_reports() _subject_duration = (time.time() - config.settings.start_time) / sum( len(files) for files in config.workflow.inputs.values() ) _subject_duration_td = datetime.timedelta(seconds=_subject_duration) time_strf = format_elapsed_time(_subject_duration_td) config.loggers.cli.log( 25, messages.PARTICIPANT_FINISHED.format(duration=time_strf), ) if _resmon is not None: from mriqc.instrumentation.viz import plot _resmon.stop() plot( _resmon._logfile, param='mem_rss_mb', out_file=str(_resmon._logfile).replace('.tsv', '.rss.png'), ) plot( _resmon._logfile, param='mem_vsm_mb', out_file=str(_resmon._logfile).replace('.tsv', '.vsm.png'), ) # Set up group level if 'group' in config.workflow.analysis_level: from mriqc.reports.group import gen_html as group_html from ..utils.misc import generate_tsv # , generate_pred config.loggers.cli.log(26, messages.GROUP_START) # Generate reports mod_group_reports = [] for mod in config.execution.modalities or config.SUPPORTED_SUFFIXES: output_dir = config.execution.output_dir dataframe, out_tsv = generate_tsv(output_dir, mod) # If there are no iqm.json files, nothing to do. if dataframe is None: continue tsv_message = messages.TSV_GENERATED.format(modality=mod, path=out_tsv) config.loggers.cli.info(tsv_message) # out_pred = generate_pred(derivatives_dir, settings['output_dir'], mod) # if out_pred is not None: # log.info('Predicted QA CSV table for the %s data generated (%s)', # mod, out_pred) out_html = output_dir / f'group_{mod}.html' group_html( out_tsv, mod, csv_failed=output_dir / f'group_variant-failed_{mod}.csv', out_file=out_html, ) report_message = messages.GROUP_REPORT_GENERATED.format(modality=mod, path=out_html) config.loggers.cli.info(report_message) mod_group_reports.append(mod) if not mod_group_reports: raise Exception(messages.GROUP_NO_DATA) config.loggers.cli.info(messages.GROUP_FINISHED) from mriqc.utils.bids import write_bidsignore, write_derivative_description config.loggers.cli.info(messages.BIDS_META) write_derivative_description(config.execution.bids_dir, config.execution.output_dir) write_bidsignore(config.execution.output_dir) _run_duration = time.time() - config.settings.start_time _run_duration_td = datetime.timedelta(seconds=_run_duration) time_strf = format_elapsed_time(_run_duration_td) config.loggers.cli.log( 26, messages.RUN_FINISHED.format(duration=time_strf), ) config.to_filename( config.execution.log_dir / f'config-{config.execution.run_uuid}.toml', store_inputs=False, # Inputs are not necessary anymore ) sys.exit(exitcode) if __name__ == '__main__': main() ================================================ FILE: mriqc/cli/version.py ================================================ # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- # vi: set ft=python sts=4 ts=4 sw=4 et: # # Copyright 2021 The NiPreps Developers # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # We support and encourage derived works from this project, please read # about our expectations at # # https://www.nipreps.org/community/licensing/ # """Version CLI helpers.""" from contextlib import suppress from datetime import datetime, timezone import requests from mriqc import __version__, config RELEASE_EXPIRY_DAYS = 14 DATE_FMT = '%Y%m%d' UTC = timezone.utc def check_latest(): """Determine whether this is the latest version.""" from packaging.version import InvalidVersion, Version latest = None date = None outdated = None cachefile = config.environment.cache_path / 'latest' try: cachefile.parent.mkdir(parents=True, exist_ok=True) except OSError: cachefile = None if cachefile and cachefile.exists(): with suppress(Exception): latest, date = cachefile.read_text().split('|') if latest and date: try: latest = Version(latest) date = datetime.strptime(date, DATE_FMT).astimezone(UTC) except (InvalidVersion, ValueError): latest = None else: if abs((datetime.now(tz=UTC) - date).days) > RELEASE_EXPIRY_DAYS: outdated = True if latest is None or outdated is True: response = None with suppress(Exception): response = requests.get(url='https://pypi.org/pypi/mriqc/json', timeout=1.0) if response and response.status_code == 200: versions = [Version(rel) for rel in response.json()['releases'].keys()] versions = [rel for rel in versions if not rel.is_prerelease] if versions: latest = sorted(versions)[-1] else: latest = None if cachefile is not None and latest is not None: with suppress(Exception): cachefile.write_text('|'.join((f'{latest}', datetime.now(tz=UTC).strftime(DATE_FMT)))) return latest def is_flagged(): """Check whether current version is flagged.""" # https://raw.githubusercontent.com/nipreps/mriqc/master/.versions.json flagged = () response = None with suppress(Exception): response = requests.get( url="""\ https://raw.githubusercontent.com/nipreps/mriqc/master/.versions.json""", timeout=1.0, ) if response and response.status_code == 200: flagged = response.json().get('flagged', {}) or {} if __version__ in flagged: return True, flagged[__version__] return False, None ================================================ FILE: mriqc/cli/workflow.py ================================================ # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- # vi: set ft=python sts=4 ts=4 sw=4 et: # # Copyright 2021 The NiPreps Developers # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # We support and encourage derived works from this project, please read # about our expectations at # # https://www.nipreps.org/community/licensing/ # """ The workflow builder factory method. All the checks and the construction of the workflow are done inside this function that has pickleable inputs and output dictionary (``retval``) to allow isolation using a ``multiprocessing.Process`` that allows dmriprep to enforce a hard-limited memory-scope. """ def build_workflow(config_file, retval): """Create the Nipype Workflow that supports the whole execution graph.""" import os from mriqc import config # We do not need OMP > 1 for workflow creation os.environ['OMP_NUM_THREADS'] = '1' os.environ['NUMEXPR_MAX_THREADS'] = '1' from mriqc.workflows.core import init_mriqc_wf config.load(config_file) # Initialize nipype config config.nipype.init() # Make sure loggers are started config.loggers.init() retval['return_code'] = 1 retval['workflow'] = None config.loggers.cli.log(25, "Building MRIQC's workflows...") retval['workflow'] = init_mriqc_wf() retval['return_code'] = int(retval['workflow'] is None) config.loggers.cli.log(25, f'Workflow building finished (exit code {retval["return_code"]}).') return retval ================================================ FILE: mriqc/config.py ================================================ # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- # vi: set ft=python sts=4 ts=4 sw=4 et: # # Copyright 2021 The NiPreps Developers # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # We support and encourage derived works from this project, please read # about our expectations at # # https://www.nipreps.org/community/licensing/ # r""" A Python module to maintain unique, run-wide *MRIQC* settings. This module implements the memory structures to keep a consistent, singleton config. Settings are passed across processes via filesystem, and a copy of the settings for each run and subject is left under ``/sub-/log//mriqc.toml``. Settings are stored using :abbr:`ToML (Tom's Markup Language)`. The module has a :py:func:`~mriqc.config.to_filename` function to allow writing out the settings to hard disk in *ToML* format, which looks like: .. literalinclude:: ../mriqc/data/config-example.toml :language: toml :name: mriqc.toml :caption: **Example file representation of MRIQC settings**. This config file is used to pass the settings across processes, using the :py:func:`~mriqc.config.load` function. Configuration sections ---------------------- .. autoclass:: environment :members: .. autoclass:: execution :members: .. autoclass:: workflow :members: .. autoclass:: nipype :members: Usage ----- A config file is used to pass settings and collect information as the execution graph is built across processes. .. code-block:: Python from mriqc import config config_file = mktemp(dir=config.execution.work_dir, prefix='.mriqc.', suffix='.toml') config.to_filename(config_file) # Call build_workflow(config_file, retval) in a subprocess with Manager() as mgr: from .workflow import build_workflow retval = mgr.dict() p = Process(target=build_workflow, args=(str(config_file), retval)) p.start() p.join() config.load(config_file) # Access configs from any code section as: value = config.section.setting Logging ------- .. autoclass:: loggers :members: Other responsibilities ---------------------- The :py:mod:`config` is responsible for other conveniency actions. * Switching Python's :obj:`multiprocessing` to *forkserver* mode. * Set up a filter for warnings as early as possible. * Automated I/O magic operations. Some conversions need to happen in the store/load processes (e.g., from/to :obj:`~pathlib.Path` \<-\> :obj:`str`, :py:class:`~bids.layout.BIDSLayout`, etc.) """ from __future__ import annotations import os import pickle import sys from collections.abc import Iterable from contextlib import suppress from pathlib import Path from time import strftime from typing import TYPE_CHECKING, Any from uuid import uuid4 try: # This option is only available with Python 3.8 from importlib.metadata import version as get_version except ImportError: from importlib_metadata import version as get_version # Ignore annoying warnings from mriqc._warnings import logging if TYPE_CHECKING: from re import Pattern from bids.layout import BIDSLayout __version__: str = get_version('mriqc') _pre_exec_env: dict[str, str] = dict(os.environ) # Reduce numpy's vms by limiting OMP_NUM_THREADS _default_omp_threads: int = int(os.getenv('OMP_NUM_THREADS', os.cpu_count())) # Disable NiPype etelemetry always _disable_et: bool = bool(os.getenv('NO_ET') is not None or os.getenv('NIPYPE_NO_ET') is not None) os.environ['NIPYPE_NO_ET'] = '1' os.environ['NO_ET'] = '1' _mriqc_dev: bool = os.getenv('MRIQC_DEV', '0').lower() in ('1', 'on', 'true', 'y', 'yes') if not hasattr(sys, '_is_pytest_session'): sys._is_pytest_session = False # Trick to avoid sklearn's FutureWarnings # Disable all warnings in main and children processes only on production versions if not any( ( '+' in __version__, __version__.endswith('.dirty'), _mriqc_dev, ) ): os.environ['PYTHONWARNINGS'] = 'ignore' SUPPORTED_SUFFIXES: tuple[str, ...] = ('T1w', 'T2w', 'bold', 'dwi') DEFAULT_MEMORY_MIN_GB: float = 0.01 DSA_MESSAGE: str = """\ IMPORTANT: Anonymized quality metrics (IQMs) will be submitted to MRIQC's metrics \ repository. \ Submission of IQMs can be disabled using the ``--no-sub`` argument. \ Please visit https://mriqc.readthedocs.io/en/latest/dsa.html to revise MRIQC's \ Data Sharing Agreement.""" _exec_env: str = os.name _docker_ver: str | None = None # special variable set in the container if os.getenv('IS_DOCKER_8395080871'): _exec_env: str = 'singularity' _cgroup: Path = Path('/proc/1/cgroup') if _cgroup.exists() and 'docker' in _cgroup.read_text(): _docker_ver = os.getenv('DOCKER_VERSION_8395080871') _exec_env = 'docker' del _cgroup _templateflow_home: Path = Path( os.getenv( 'TEMPLATEFLOW_HOME', os.path.join(os.getenv('HOME'), '.cache', 'templateflow'), ) ) _free_mem_at_start: int | None = None with suppress(Exception): from psutil import virtual_memory _free_mem_at_start: int = round(virtual_memory().free / 1024**3, 1) _oc_limit: str = 'n/a' _oc_policy: str = 'n/a' with suppress(Exception): # Memory policy may have a large effect on types of errors experienced _proc_oc_path: Path = Path('/proc/sys/vm/overcommit_memory') if _proc_oc_path.exists(): _oc_policy: str = {'0': 'heuristic', '1': 'always', '2': 'never'}.get( _proc_oc_path.read_text().strip(), 'unknown' ) if _oc_policy != 'never': _proc_oc_kbytes: Path = Path('/proc/sys/vm/overcommit_kbytes') if _proc_oc_kbytes.exists(): _oc_limit: str = _proc_oc_kbytes.read_text().strip() if _oc_limit in ('0', 'n/a') and Path('/proc/sys/vm/overcommit_ratio').exists(): _oc_limit: str = '{}%'.format( Path('/proc/sys/vm/overcommit_ratio').read_text().strip() ) _memory_gb: float | None = None if 'linux' in sys.platform: with suppress(Exception): with open('/proc/meminfo') as f_in: _meminfo_lines: list[str] = f_in.readlines() _mem_total_line: str = [line for line in _meminfo_lines if 'MemTotal' in line][0] _mem_total: float = float(_mem_total_line.split()[1]) _memory_gb = _mem_total / (1024.0**2) elif 'darwin' in sys.platform: from shutil import which from subprocess import check_output if _cmd := which('sysctl'): with suppress(Exception): _mem_str: str = check_output([_cmd, 'hw.memsize']).decode().strip().split(' ')[-1] _memory_gb: float = float(_mem_str) / (1024.0**3) # Check for FreeSurfer's SynthStrip model _fs_home: str | None = os.getenv('FREESURFER_HOME', None) _default_model_path: Path | None = ( Path(_fs_home) / 'models' / 'synthstrip.1.pt' if _fs_home else None ) if _fs_home and not _default_model_path.exists(): _default_model_path = None # Override the unique ID if MRIQC_DEV is set _run_uuid: str = ( '{}_{}'.format(strftime('%Y%m%d-%H%M%S'), uuid4()) if not _mriqc_dev else '18480913-163000_PhineasG-ageh-adhi-sacc-ident9b1ab0f' ) _mriqc_cache: Path = Path( os.getenv('MRIQC_CACHE_PATH', str(Path.home() / '.cache' / 'mriqc')) ).absolute() class _Config: """An abstract class forbidding instantiation.""" _paths: tuple[str, ...] = () _hidden: tuple[str, ...] = () def __init__(self): """Avert instantiation.""" raise RuntimeError('Configuration type is not instantiable.') @classmethod def load(cls, sections, init=True): """Store settings from a dictionary.""" for k, v in sections.items(): if v is None: continue if k in cls._paths: setattr(cls, k, Path(v).absolute()) continue if hasattr(cls, k): setattr(cls, k, v) if init: try: cls.init() except AttributeError: pass @classmethod def get(cls) -> dict[str, Any]: """Return defined settings.""" out: dict[str, str] = {} for k, v in cls.__dict__.items(): if k.startswith('_') or v is None: continue if k in cls._hidden: continue if callable(getattr(cls, k)): continue if k in cls._paths: v = str(v) out[k] = v return out class settings(_Config): """Settings of this config module.""" file_path: Path | None = None """Path to this configuration file.""" start_time: float | None = None """A :obj:`~time.time` timestamp at the time the workflow is started.""" _paths: tuple[str, ...] = ('file_path',) class environment(_Config): """ Read-only options regarding the platform and environment. Crawls runtime descriptive settings (e.g., default FreeSurfer license, execution environment, nipype and *MRIQC* versions, etc.). The ``environment`` section is not loaded in from file, only written out when settings are exported. This config section is useful when reporting issues, and these variables are tracked whenever the user does not opt-out using the ``--notrack`` argument. """ cache_path: Path = _mriqc_cache """Path to the location of the cache directory.""" cpu_count: int = os.cpu_count() """Number of available CPUs.""" exec_docker_version: str | None = _docker_ver """Version of Docker Engine.""" exec_env: str = _exec_env """A string representing the execution platform.""" free_mem: int | None = _free_mem_at_start """Free memory at start.""" freesurfer_home: str | None = _fs_home """Path to the *FreeSurfer* installation (from ``FREESURFER_HOME`` environment variable).""" overcommit_policy: str = _oc_policy """Linux's kernel virtual memory overcommit policy.""" overcommit_limit: str = _oc_limit """Linux's kernel virtual memory overcommit limits.""" nipype_version: str = get_version('nipype') """Nipype's current version.""" synthstrip_path: Path | None = _default_model_path """Path to *SynthStrip*'s model weights (requires *FreeSurfer*).""" templateflow_version: str = get_version('templateflow') """The TemplateFlow client version installed.""" total_memory: float | None = _memory_gb """Total memory available, in GB.""" version: str = __version__ """*MRIQC*'s version.""" _pre_mriqc: dict[str, str] = _pre_exec_env """Environment variables before MRIQC's execution.""" class nipype(_Config): """Nipype settings.""" crashfile_format: str = 'txt' """The file format for crashfiles, either text or pickle.""" get_linked_libs: bool = False """Run NiPype's tool to enlist linked libraries for every interface.""" local_hash_check: bool = True """Check if interface is cached locally before executing.""" memory_gb: float | str | None = None """Estimation in GB of the RAM this workflow can allocate at any given time.""" nprocs: int = os.cpu_count() """Number of processes (compute tasks) that can be run in parallel (multiprocessing only).""" omp_nthreads: int = _default_omp_threads """Number of CPUs a single process can access for multithreaded execution.""" plugin: str = 'MultiProc' """NiPype's execution plugin.""" plugin_args: dict[str, Any] = { 'maxtasksperchild': 1, 'raise_insufficient': False, } """Settings for NiPype's execution plugin.""" remove_node_directories: bool = False """Remove directories whose outputs have already been used up.""" resource_monitor: bool = False """Enable resource monitor.""" stop_on_first_crash: bool = True """Whether the workflow should stop or continue after the first error.""" @classmethod def get_plugin(cls) -> dict[str, Any]: """Format a dictionary for Nipype consumption.""" out: dict[str, Any] = { 'plugin': cls.plugin, 'plugin_args': cls.plugin_args, } if cls.plugin in ('MultiProc', 'LegacyMultiProc'): out['plugin_args']['n_procs'] = int(cls.nprocs) if cls.memory_gb: out['plugin_args']['memory_gb'] = float(cls.memory_gb) return out @classmethod def init(cls) -> None: """Set NiPype configurations.""" from nipype import config as ncfg # Nipype config (logs and execution) ncfg.update_config( { 'execution': { 'crashdump_dir': str(execution.log_dir), 'crashfile_format': cls.crashfile_format, 'get_linked_libs': cls.get_linked_libs, 'stop_on_first_crash': cls.stop_on_first_crash, } } ) class execution(_Config): """Configure run-level settings.""" ants_float: bool = False """Use float number precision for ANTs computations.""" bids_dir: str | os.PathLike | None = None """An existing path to the dataset, which must be BIDS-compliant.""" bids_dir_datalad: bool = False """Whether the input directory seems under DataLad's version control.""" bids_database_dir: str | os.PathLike | None = None """Path to the directory containing SQLite database indices for the input BIDS dataset.""" bids_database_wipe: bool = False """Wipe out previously existing BIDS indexing caches, forcing re-indexing.""" bids_description_hash: str | None = None """Checksum (SHA256) of the ``dataset_description.json`` of the BIDS dataset.""" bids_filters: dict | None = None """A dictionary describing custom BIDS input filter using PyBIDS.""" cwd: str = os.getcwd() """Current working directory.""" datalad_get: bool = True """ If :obj:`~mriqc.config.execution.bids_dir` is a DataLad dataset, a ``datalad get`` command will be executed to ensure all remote files have been fetched. This behavior can be disabled with the command line option ``--no-datalad-get``. """ debug: bool = False """Run in sloppy mode (meaning, suboptimal parameters that minimize run-time).""" dry_run: bool = False """Just test, do not run.""" dsname: str = '' """A dataset name used when generating files from the rating widget.""" echo_id: str | None = None """Select a particular echo for multi-echo EPI datasets.""" float32: bool = True """Cast the input data to float32 if it's represented with higher precision.""" layout: BIDSLayout | None = None """A :py:class:`~bids.layout.BIDSLayout` object, see :py:func:`init`.""" log_dir: str | os.PathLike | None = None """The path to a directory that contains execution logs.""" log_level: int | str = 25 """Output verbosity.""" modalities: Iterable[str] = None """Filter input dataset by MRI type.""" no_sub: bool = False """Turn off submission of anonymized quality metrics to Web API.""" notrack: bool = False """Disable the sharing of usage information with developers.""" output_dir: str | os.PathLike | None = None """Folder where derivatives will be stored.""" participant_label: Iterable[str] | None = None """List of participant identifiers that are to be preprocessed.""" pdb: bool = False """Drop into PDB when exceptions are encountered.""" reports_only: bool = False """Only build the reports, based on the reportlets found in a cached working directory.""" resource_monitor: bool = False """Enable resource monitor.""" run_id: str | None = None """Filter input dataset by run identifier.""" run_uuid: str = _run_uuid """Unique identifier of this particular run.""" session_id: str | None = None """Filter input dataset by session identifier.""" task_id: str | None = None """Select a particular task from all available in the dataset.""" templateflow_home: str | os.PathLike | None = _templateflow_home """The root folder of the TemplateFlow client.""" upload_strict: bool = False """Workflow will crash if upload is not successful.""" verbose_reports: bool = False """Generate extended reports.""" webapi_token: str = '' """Authorization token for the WebAPI service.""" webapi_url: str = 'https://mriqc.nimh.nih.gov:443/api/v1' """IP address where the MRIQC WebAPI is listening.""" work_dir: Path = Path('work').absolute() """Path to a working directory where intermediate results will be available.""" write_graph: bool = False """Write out the computational graph corresponding to the planned preprocessing.""" _layout: BIDSLayout | None = None _paths: tuple[str, ...] = ( 'anat_derivatives', 'bids_dir', 'bids_database_dir', 'fs_license_file', 'fs_subjects_dir', 'log_dir', 'output_dir', 'templateflow_home', 'work_dir', ) _hidden: tuple[str, ...] = ('webapi_token',) @classmethod def init(cls) -> None: """Create a new BIDS Layout accessible with :attr:`~execution.layout`.""" if cls.bids_filters is None: cls.bids_filters = {} # Process --run-id if the argument was provided if cls.run_id: for mod in cls.modalities: cls.bids_filters.setdefault(mod.lower(), {})['run'] = cls.run_id if cls._layout is None: import re from bids.layout import BIDSLayout from bids.layout.index import BIDSLayoutIndexer ignore_paths: list[Pattern] = [ # Ignore folders at the top if they don't start with /sub-