diff --git a/.codespellignore b/.codespellignore deleted file mode 100644 index e69de29..0000000 diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..9f60de4 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,74 @@ +# Python Build Artifacts +.Python +**/__pycache__/ +**/*.py[cod] +**/build/ +**/develop-eggs/ +**/dist/ +**/downloads/ +**/eggs/ +**/.eggs/ +**/lib/ +**/lib64/ +**/parts/ +**/sdist/ +**/var/ +**/wheels/ +**/*.egg-info/ +**/.installed.cfg +**/*.egg +**/MANIFEST + +# Test & Coverage Files +.pytest_cache/ +.ruff_cache/ +.tox/ +.nox/ +coverage.xml +.coverage +.coverage.* +htmlcov/ +junit-results.xml +tests/ + +# Version Control +.git/ +.gitignore +.gitattributes +.github/ + +# Editor / IDE Files +.ipynb_checkpoints +.idea/ +.vscode/ +*.swp +*.swo +*~ + +# Virtual Environments +venv/ +.venv/ +env/ +.env/ + +# Docker Files +.dockerignore +docker +docker/Dockerfile + +# Singularity files +singularity +*sif + +# Local Config Files +.env +*.env.* +*.pem +*.key + +# OS files +.DS_Store +Thumbs.db + +# Documentation +howto* diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2f50829..20faa78 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,7 +15,7 @@ jobs: fail-fast: false matrix: os: [ubuntu, macos, windows] - python-version: ["3.9", "3.10", "3.11", "3.12"] + python-version: ["3.10", "3.11", "3.12", "3.13"] name: ${{ matrix.os }} - py${{ matrix.python-version }} runs-on: ${{ matrix.os }}-latest defaults: diff --git a/.github/workflows/code-style.yml b/.github/workflows/code-style.yml index 8571683..3a14cd4 100644 --- a/.github/workflows/code-style.yml +++ b/.github/workflows/code-style.yml @@ -15,10 +15,10 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v6 - - name: Setup Python 3.10 + - name: Setup Python 3.13 uses: actions/setup-python@v6 with: - python-version: '3.10' + python-version: '3.13' architecture: 'x64' - name: Install dependencies run: | @@ -28,8 +28,3 @@ jobs: run: ruff check . - name: Run codespell uses: codespell-project/actions-codespell@master - with: - check_filenames: true - check_hidden: true - skip: './.git,./build,./.mypy_cache,./.pytest_cache' - ignore_words_file: ./.codespellignore diff --git a/.github/workflows/doc.yml b/.github/workflows/doc.yml index 5f89ded..b562374 100644 --- a/.github/workflows/doc.yml +++ b/.github/workflows/doc.yml @@ -20,10 +20,10 @@ jobs: uses: actions/checkout@v6 with: path: ./main - - name: Setup Python 3.10 + - name: Setup Python 3.13 uses: actions/setup-python@v6 with: - python-version: '3.10' + python-version: '3.13' architecture: 'x64' - name: Install package run: | diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 297f1e4..4a8b5df 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -11,10 +11,10 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v6 - - name: Setup Python 3.10 + - name: Setup Python 3.13 uses: actions/setup-python@v6 with: - python-version: '3.10' + python-version: '3.13' architecture: 'x64' - name: Install dependencies run: | diff --git a/.gitignore b/.gitignore index 4526199..d8f28d1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,19 @@ -# -misc -deprecated -testing +# tests +tests +.pytest_cache/ +.ruff_cache/ +junit-results.xml +coverage.xml -# macOS +# OS files .DS_Store +Thumbs.db -# -.flake8 +# documentation +howto* + +# images +*sif # .ipynb_checkpoints @@ -22,19 +28,19 @@ __pycache__/ # Distribution / packaging .Python +.installed.cfg build/ -develop-eggs/ dist/ downloads/ +develop-eggs/ eggs/ .eggs/ +*.egg-info/ +*.egg lib/ lib64/ parts/ sdist/ var/ wheels/ -*.egg-info/ -.installed.cfg -*.egg MANIFEST diff --git a/README.md b/README.md index 38b86db..554d91d 100644 --- a/README.md +++ b/README.md @@ -465,7 +465,7 @@ ___ - At least one structural MR image that was processed with Freesurfer 6.0, 7.x, or FastSurfer 1.1 or later (including the surface pipeline). -- A Python version >= 3.8 is required to run this script. +- A Python version >= 3.10 is required to run this script. - Required packages include (among others) the nibabel and skimage package for the core functionality, plus the matplotlib, pandas, and transform3d diff --git a/docker/Docker.md b/docker/Docker.md index c283fe3..cb0046a 100644 --- a/docker/Docker.md +++ b/docker/Docker.md @@ -6,16 +6,18 @@ The docker image will be based on Ubuntu, contain the fsqc scripts, the lapy and ## Build fsqc Docker image -To build the docker image, execute the following command after traversing into the *docker* directory of this repository: +To build the docker image, first download the software from its GitHub repository at `https://github.com/Deep-MI/fsqc`, or clone it directly via `git clone https://github.com/Deep-MI/fsqc`. + +Next, execute the following **docker** command from the project's main directory, i.e. `fsqc` (not `fsqc/fsqc`): ```bash -docker build --rm -t deepmi/fsqcdocker -f Dockerfile . +docker build --rm -t deepmi/fsqcdocker -f docker/Dockerfile . ``` -The name of the image will be `deepmi/fsqcdocker`, and it will be built from the `Dockerfile` configuration file from the *docker* directory. You have some flexibility in choosing the name, so this is just an example. +The name of the image will be `deepmi/fsqcdocker`, and it will be built from the `Dockerfile` configuration file from the *docker* directory. You have some flexibility in choosing the name, so this is just an example. Note that the `fsqc` directory will be the *build context* for creating the docker image; if you do not want to include any particular files or directories into the image, add them to the `.dockerignore` file. The `--rm` flag will remove intermediate containers after a successful build; `-t` specifies the name of the image, and `-f` indicates the configuration file from which to build. -Take a look at the contents of the [`Dockerfile`](Dockerfile) to see what is done during the build process: essentially, it is getting the Ubuntu 22.04 image, installing additional packages from the distribution, downloading the fsqc toolbox, and setting the necessary environment variables. Unless the `Dockerfile` changes, the build process has to be done only once. +Take a look at the contents of the [`Dockerfile`](Dockerfile) to see what is done during the build process: essentially, it is getting the Python3.11 image, installing additional packages, copying and installing the fsqc toolbox, and setting the necessary environment variables. Unless the `Dockerfile` changes, the build process has to be done only once. ## Download the Docker image diff --git a/docker/Dockerfile b/docker/Dockerfile index 4ae358d..8790fb5 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,45 +1,57 @@ # get OS -FROM ubuntu:22.04 - -# update OS -RUN apt-get update +FROM python:3.11 # get additional packages -RUN apt-get install -y --no-install-recommends \ - gcc \ - git \ +RUN apt-get update && apt-get install -y --no-install-recommends \ time \ zlib1g-dev \ libjpeg-dev \ - python3 \ - python3-dev \ - python3-pip \ - python3-setuptools \ - python3-wheel \ libxcb-xinerama0 \ - xvfb \ - libopengl0 \ - libegl1-mesa - -# clone fsqc -RUN git clone https://github.com/Deep-MI/fsqc.git /app/fsqc + libegl1 \ + libgl1 \ + libfontconfig1 && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* # upgrade pip -RUN pip3 install --upgrade pip +RUN pip install --upgrade pip + +# +RUN mkdir -p /app/fsqc -# install additional python packages (not necessary if using pip install) -RUN pip3 install -r /app/fsqc/requirements.txt +# +COPY pyproject.toml /app/fsqc +COPY setup.py /app/fsqc +COPY requirements.txt /app/fsqc +COPY DESCRIPTION.md /app/fsqc +COPY LICENSE /app/fsqc +COPY VERSION /app/fsqc +COPY fsqc /app/fsqc/fsqc +COPY run_fsqc /app/fsqc + +# install fsqc as a package +RUN pip install /app/fsqc # Add other environment variables ENV OMP_NUM_THREADS=1 ENV MPLCONFIGDIR=/tmp/mplconfigdir -# Set the working directory to /app -WORKDIR /app +# Suppress Mesa's shader-cache warning ("Failed to create //.cache …") that +# appears when running as a non-standard user inside Docker where $HOME is +# unset or points to a non-writable directory. +ENV MESA_SHADER_CACHE_DISABLE=1 + +# In order to find NVIDIA GPUs (--gpus all) +ENV NVIDIA_VISIBLE_DEVICES=all +ENV NVIDIA_DRIVER_CAPABILITIES=all -# Set entrypoint (non-interactive mode) -ENTRYPOINT ["python3", "/app/fsqc/run_fsqc"] +# Register the NVIDIA EGL ICD so libEGL finds the GPU driver +RUN mkdir -p /usr/share/glvnd/egl_vendor.d && \ + echo '{"file_format_version":"1.0.0","ICD":{"library_path":"libEGL_nvidia.so.0"}}' \ + > /usr/share/glvnd/egl_vendor.d/10_nvidia.json -## Run app when the container launches (interactive mode) -#CMD ["/bin/bash"] +# Set the working directory to /app +WORKDIR /app +# Set entrypoint +ENTRYPOINT ["run_fsqc"] diff --git a/fsqc/createScreenshots.py b/fsqc/createScreenshots.py index 7a5a409..6abf047 100644 --- a/fsqc/createScreenshots.py +++ b/fsqc/createScreenshots.py @@ -208,7 +208,7 @@ def computeLayout(n): lut = np.concatenate((lut, lutAdd), axis=0) - lutEnum = dict(zip(lut[:, 0], range(len(lut[:, 0])))) + lutEnum = dict(zip(lut[:, 0], range(len(lut[:, 0])), strict=True)) lutTab = np.array(lut[:, (2, 3, 4, 5)] / 255, dtype="float32") lutTab[:, 3] = 1 diff --git a/fsqc/createSurfacePlots.py b/fsqc/createSurfacePlots.py index 0be5780..a6fc70c 100644 --- a/fsqc/createSurfacePlots.py +++ b/fsqc/createSurfacePlots.py @@ -30,8 +30,7 @@ def createSurfacePlots(SUBJECT, SUBJECTS_DIR, SURFACES_OUTDIR, VIEWS, FASTSURFER import os - from whippersnappy import snap1 - from whippersnappy.utils.types import ViewType + from whippersnappy import ViewType, snap1 # ----------------------------------------------------------------------------- # import surfaces and overlays diff --git a/fsqc/fsqcMain.py b/fsqc/fsqcMain.py index c90c48e..525037b 100644 --- a/fsqc/fsqcMain.py +++ b/fsqc/fsqcMain.py @@ -323,7 +323,7 @@ def get_help(print_help=True, return_help=False): At least one subject whose structural MR image was processed with Freesurfer 6.0 or later, or FastSurfer v1.1 or later (including the surface pipeline). - A Python version >= 3.8 is required to run this script. + A Python version >= 3.10 is required to run this script. Required packages include (among others) the nibabel and skimage package for the core functionality, plus the the matplotlib, pandas, and transform3d @@ -2438,7 +2438,8 @@ def _do_fsqc(argsDict): "fornixShapeEV{:0>3}".format, range(FORNIX_N_EIGEN), ), - fornixShapeOutput, + fornixShapeOutput, + strict=True, ) ) } @@ -2455,7 +2456,8 @@ def _do_fsqc(argsDict): "fornixShapeEV{:0>3}".format, range(FORNIX_N_EIGEN), ), - np.full(FORNIX_N_EIGEN, np.nan), + np.full(FORNIX_N_EIGEN, np.nan), + strict=True, ) ) } @@ -2486,7 +2488,8 @@ def _do_fsqc(argsDict): "fornixShapeEV{:0>3}".format, range(FORNIX_N_EIGEN), ), - fornixShapeOutput, + fornixShapeOutput, + strict=True, ) ) } @@ -2899,7 +2902,8 @@ def _do_fsqc(argsDict): map( "fornixShapeEV{:0>3}".format, range(FORNIX_N_EIGEN) ), - fornixShapeOutput, + fornixShapeOutput, + strict=True, ) ) } diff --git a/fsqc/outlierDetection.py b/fsqc/outlierDetection.py index 6e30911..f3f4c2c 100644 --- a/fsqc/outlierDetection.py +++ b/fsqc/outlierDetection.py @@ -307,7 +307,7 @@ def readAparcStats(path_aparc_stats, hemi): + hemi + "." + "bankssts": dict( - zip(col_headers[1:], [float(x) for x in line.split()[1:]]) + zip(col_headers[1:], [float(x) for x in line.split()[1:]], strict=True) ) } ) @@ -319,7 +319,7 @@ def readAparcStats(path_aparc_stats, hemi): + hemi + "." + "caudalanteriorcingulate": dict( - zip(col_headers[1:], [float(x) for x in line.split()[1:]]) + zip(col_headers[1:], [float(x) for x in line.split()[1:]], strict=True) ) } ) @@ -331,7 +331,7 @@ def readAparcStats(path_aparc_stats, hemi): + hemi + "." + "caudalmiddlefrontal": dict( - zip(col_headers[1:], [float(x) for x in line.split()[1:]]) + zip(col_headers[1:], [float(x) for x in line.split()[1:]], strict=True) ) } ) @@ -343,7 +343,7 @@ def readAparcStats(path_aparc_stats, hemi): + hemi + "." + "cuneus": dict( - zip(col_headers[1:], [float(x) for x in line.split()[1:]]) + zip(col_headers[1:], [float(x) for x in line.split()[1:]], strict=True) ) } ) @@ -355,7 +355,7 @@ def readAparcStats(path_aparc_stats, hemi): + hemi + "." + "entorhinal": dict( - zip(col_headers[1:], [float(x) for x in line.split()[1:]]) + zip(col_headers[1:], [float(x) for x in line.split()[1:]], strict=True) ) } ) @@ -367,7 +367,7 @@ def readAparcStats(path_aparc_stats, hemi): + hemi + "." + "fusiform": dict( - zip(col_headers[1:], [float(x) for x in line.split()[1:]]) + zip(col_headers[1:], [float(x) for x in line.split()[1:]], strict=True) ) } ) @@ -379,7 +379,7 @@ def readAparcStats(path_aparc_stats, hemi): + hemi + "." + "inferiorparietal": dict( - zip(col_headers[1:], [float(x) for x in line.split()[1:]]) + zip(col_headers[1:], [float(x) for x in line.split()[1:]], strict=True) ) } ) @@ -391,7 +391,7 @@ def readAparcStats(path_aparc_stats, hemi): + hemi + "." + "inferiortemporal": dict( - zip(col_headers[1:], [float(x) for x in line.split()[1:]]) + zip(col_headers[1:], [float(x) for x in line.split()[1:]], strict=True) ) } ) @@ -403,7 +403,7 @@ def readAparcStats(path_aparc_stats, hemi): + hemi + "." + "isthmuscingulate": dict( - zip(col_headers[1:], [float(x) for x in line.split()[1:]]) + zip(col_headers[1:], [float(x) for x in line.split()[1:]], strict=True) ) } ) @@ -415,7 +415,7 @@ def readAparcStats(path_aparc_stats, hemi): + hemi + "." + "lateraloccipital": dict( - zip(col_headers[1:], [float(x) for x in line.split()[1:]]) + zip(col_headers[1:], [float(x) for x in line.split()[1:]], strict=True) ) } ) @@ -427,7 +427,7 @@ def readAparcStats(path_aparc_stats, hemi): + hemi + "." + "lateralorbitofrontal": dict( - zip(col_headers[1:], [float(x) for x in line.split()[1:]]) + zip(col_headers[1:], [float(x) for x in line.split()[1:]], strict=True) ) } ) @@ -439,7 +439,7 @@ def readAparcStats(path_aparc_stats, hemi): + hemi + "." + "lingual": dict( - zip(col_headers[1:], [float(x) for x in line.split()[1:]]) + zip(col_headers[1:], [float(x) for x in line.split()[1:]], strict=True) ) } ) @@ -451,7 +451,7 @@ def readAparcStats(path_aparc_stats, hemi): + hemi + "." + "medialorbitofrontal": dict( - zip(col_headers[1:], [float(x) for x in line.split()[1:]]) + zip(col_headers[1:], [float(x) for x in line.split()[1:]], strict=True) ) } ) @@ -463,7 +463,7 @@ def readAparcStats(path_aparc_stats, hemi): + hemi + "." + "middletemporal": dict( - zip(col_headers[1:], [float(x) for x in line.split()[1:]]) + zip(col_headers[1:], [float(x) for x in line.split()[1:]], strict=True) ) } ) @@ -475,7 +475,7 @@ def readAparcStats(path_aparc_stats, hemi): + hemi + "." + "parahippocampal": dict( - zip(col_headers[1:], [float(x) for x in line.split()[1:]]) + zip(col_headers[1:], [float(x) for x in line.split()[1:]], strict=True) ) } ) @@ -487,7 +487,7 @@ def readAparcStats(path_aparc_stats, hemi): + hemi + "." + "paracentral": dict( - zip(col_headers[1:], [float(x) for x in line.split()[1:]]) + zip(col_headers[1:], [float(x) for x in line.split()[1:]], strict=True) ) } ) @@ -499,7 +499,7 @@ def readAparcStats(path_aparc_stats, hemi): + hemi + "." + "parsopercularis": dict( - zip(col_headers[1:], [float(x) for x in line.split()[1:]]) + zip(col_headers[1:], [float(x) for x in line.split()[1:]], strict=True) ) } ) @@ -511,7 +511,7 @@ def readAparcStats(path_aparc_stats, hemi): + hemi + "." + "parsorbitalis": dict( - zip(col_headers[1:], [float(x) for x in line.split()[1:]]) + zip(col_headers[1:], [float(x) for x in line.split()[1:]], strict=True) ) } ) @@ -523,7 +523,7 @@ def readAparcStats(path_aparc_stats, hemi): + hemi + "." + "parstriangularis": dict( - zip(col_headers[1:], [float(x) for x in line.split()[1:]]) + zip(col_headers[1:], [float(x) for x in line.split()[1:]], strict=True) ) } ) @@ -535,7 +535,7 @@ def readAparcStats(path_aparc_stats, hemi): + hemi + "." + "pericalcarine": dict( - zip(col_headers[1:], [float(x) for x in line.split()[1:]]) + zip(col_headers[1:], [float(x) for x in line.split()[1:]], strict=True) ) } ) @@ -547,7 +547,7 @@ def readAparcStats(path_aparc_stats, hemi): + hemi + "." + "postcentral": dict( - zip(col_headers[1:], [float(x) for x in line.split()[1:]]) + zip(col_headers[1:], [float(x) for x in line.split()[1:]], strict=True) ) } ) @@ -559,7 +559,7 @@ def readAparcStats(path_aparc_stats, hemi): + hemi + "." + "posteriorcingulate": dict( - zip(col_headers[1:], [float(x) for x in line.split()[1:]]) + zip(col_headers[1:], [float(x) for x in line.split()[1:]], strict=True) ) } ) @@ -571,7 +571,7 @@ def readAparcStats(path_aparc_stats, hemi): + hemi + "." + "precentral": dict( - zip(col_headers[1:], [float(x) for x in line.split()[1:]]) + zip(col_headers[1:], [float(x) for x in line.split()[1:]], strict=True) ) } ) @@ -583,7 +583,7 @@ def readAparcStats(path_aparc_stats, hemi): + hemi + "." + "precuneus": dict( - zip(col_headers[1:], [float(x) for x in line.split()[1:]]) + zip(col_headers[1:], [float(x) for x in line.split()[1:]], strict=True) ) } ) @@ -595,7 +595,7 @@ def readAparcStats(path_aparc_stats, hemi): + hemi + "." + "rostralanteriorcingulate": dict( - zip(col_headers[1:], [float(x) for x in line.split()[1:]]) + zip(col_headers[1:], [float(x) for x in line.split()[1:]], strict=True) ) } ) @@ -607,7 +607,7 @@ def readAparcStats(path_aparc_stats, hemi): + hemi + "." + "rostralmiddlefrontal": dict( - zip(col_headers[1:], [float(x) for x in line.split()[1:]]) + zip(col_headers[1:], [float(x) for x in line.split()[1:]], strict=True) ) } ) @@ -619,7 +619,7 @@ def readAparcStats(path_aparc_stats, hemi): + hemi + "." + "superiorfrontal": dict( - zip(col_headers[1:], [float(x) for x in line.split()[1:]]) + zip(col_headers[1:], [float(x) for x in line.split()[1:]], strict=True) ) } ) @@ -631,7 +631,7 @@ def readAparcStats(path_aparc_stats, hemi): + hemi + "." + "superiorparietal": dict( - zip(col_headers[1:], [float(x) for x in line.split()[1:]]) + zip(col_headers[1:], [float(x) for x in line.split()[1:]], strict=True) ) } ) @@ -643,7 +643,7 @@ def readAparcStats(path_aparc_stats, hemi): + hemi + "." + "superiortemporal": dict( - zip(col_headers[1:], [float(x) for x in line.split()[1:]]) + zip(col_headers[1:], [float(x) for x in line.split()[1:]], strict=True) ) } ) @@ -655,7 +655,7 @@ def readAparcStats(path_aparc_stats, hemi): + hemi + "." + "supramarginal": dict( - zip(col_headers[1:], [float(x) for x in line.split()[1:]]) + zip(col_headers[1:], [float(x) for x in line.split()[1:]], strict=True) ) } ) @@ -667,7 +667,7 @@ def readAparcStats(path_aparc_stats, hemi): + hemi + "." + "frontalpole": dict( - zip(col_headers[1:], [float(x) for x in line.split()[1:]]) + zip(col_headers[1:], [float(x) for x in line.split()[1:]], strict=True) ) } ) @@ -679,7 +679,7 @@ def readAparcStats(path_aparc_stats, hemi): + hemi + "." + "temporalpole": dict( - zip(col_headers[1:], [float(x) for x in line.split()[1:]]) + zip(col_headers[1:], [float(x) for x in line.split()[1:]], strict=True) ) } ) @@ -691,7 +691,7 @@ def readAparcStats(path_aparc_stats, hemi): + hemi + "." + "transversetemporal": dict( - zip(col_headers[1:], [float(x) for x in line.split()[1:]]) + zip(col_headers[1:], [float(x) for x in line.split()[1:]], strict=True) ) } ) @@ -703,7 +703,7 @@ def readAparcStats(path_aparc_stats, hemi): + hemi + "." + "insula": dict( - zip(col_headers[1:], [float(x) for x in line.split()[1:]]) + zip(col_headers[1:], [float(x) for x in line.split()[1:]], strict=True) ) } ) @@ -1072,10 +1072,10 @@ def outlierDetection( iqr = np.percentile(df, 75, axis=0) - np.percentile(df, 25, axis=0) sample_nonpar_lower = dict( - zip(df.columns, np.percentile(df, 25, axis=0) - 1.5 * iqr) + zip(df.columns, np.percentile(df, 25, axis=0) - 1.5 * iqr, strict=True) ) sample_nonpar_upper = dict( - zip(df.columns, np.percentile(df, 75, axis=0) + 1.5 * iqr) + zip(df.columns, np.percentile(df, 75, axis=0) + 1.5 * iqr, strict=True) ) sample_param_lower = dict(np.mean(df, axis=0) - 2 * np.std(df, axis=0)) diff --git a/fsqc/utils/_config.py b/fsqc/utils/_config.py index 3b2b5a3..945647c 100644 --- a/fsqc/utils/_config.py +++ b/fsqc/utils/_config.py @@ -1,14 +1,15 @@ import platform import re import sys +from collections.abc import Callable from functools import partial from importlib.metadata import requires, version -from typing import IO, Callable, Optional +from typing import IO import psutil -def sys_info(fid: Optional[IO] = None, developer: bool = False): +def sys_info(fid: IO | None = None, developer: bool = False): """Print the system information for debugging. Parameters diff --git a/pyproject.toml b/pyproject.toml index 289aef8..0413f71 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = 'setuptools.build_meta' name = 'fsqc' description = 'Quality control scripts for FastSurfer and FreeSurfer structural MRI data' license = {file = 'LICENSE'} -requires-python = '>=3.9' +requires-python = '>=3.10' authors = [ {name = 'Kersten Diers', email = 'kersten.diers@dzne.de'}, {name = 'Martin Reuter', email = 'martin.reuter@dzne.de'} @@ -25,10 +25,10 @@ classifiers = [ 'Operating System :: Unix', 'Operating System :: MacOS', 'Programming Language :: Python :: 3 :: Only', - 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', + 'Programming Language :: Python :: 3.13', 'Natural Language :: English', 'License :: OSI Approved :: MIT License', 'Intended Audience :: Science/Research', @@ -116,7 +116,6 @@ extend-exclude = [ ] ignore = ["E501"] # line too long (should be enforced soon) - [tool.ruff.lint] # https://docs.astral.sh/ruff/linter/#rule-selection select = [ @@ -125,7 +124,6 @@ select = [ "UP", # pyupgrade "B", # flake8-bugbear "I", # isort - # "SIM", # flake8-simplify ] [tool.ruff.lint.per-file-ignores] @@ -141,6 +139,11 @@ addopts = [ "--verbose", ] +[tool.codespell] +check-filenames = true +check-hidden = true +skip = './.git,./build,./.mypy_cache,./.pytest_cache' + [tool.coverage.run] branch = true cover_pylib = false diff --git a/requirements.txt b/requirements.txt index 184b5ce..270de9c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,4 +12,4 @@ glfw pillow pyrr PyQt6 -whippersnappy>=2.0.0 +whippersnappy>=2.1.0 diff --git a/singularity/Singularity.md b/singularity/Singularity.md index e6c0e86..5e57624 100644 --- a/singularity/Singularity.md +++ b/singularity/Singularity.md @@ -26,15 +26,15 @@ For how to create your own Docker images see our [Docker guide](../docker/Docker ```bash singularity exec \ -B /path/to/subjects/directory:/data \ - -B /path/to/subjects/directory:/out \ + -B /path/to/output/directory:/out \ /home/user/my_singularity_images/fsqc-myimage.sif \ - xvfb-run /app/fsqc/run_fsqc \ + /app/fsqc/run_fsqc \ --subjects_dir /data \ --output_dir /out ``` * The first two `-B` arguments mount your data directory and output directories into the singularity image (note that full, not relative, pathnames should be given). Inside the image, they are visible under the name following the colon (in this case `/path_to_filename_inside_container` and `/path_to_output_directory_inside_container`, but these can be different). From within the singularity image / container, there will be read and write access to the directories that are mounted into the image (unless specified otherwise). * The next part of the command is the name of the Singularity image, which is `fsqc-myimage.sif` in this example, but can be freely chosen depending on what was set during the build process (see above). In this example, the image is located in `/home/user/my_singularity_images`, but the specific path will likely be different on your local system. -* For the Singularity image, we also have to explicitly specify the command that we to want run, i.e. `/app/fsqc/run_fsqc`. If off-screen rendering is intended, for example surface rendering via the `--surfaces` or `--surfaces-html` arguments during processing on a server without a dedicated display, we also have to add the `xvfb-run` program: `xvfb-run /app/fsqc/run_fsqc`. `xvfb-run` is a command that lets you run graphical programs on a computer without a real screen, i.e. creates a virtual graphical display that will be used for rendering. If no off-screen rendering takes place, it does not matter if `xvfb-run` is added or omitted. +* For the Singularity image, we also have to explicitly specify the command that we want to run, i.e. `/app/fsqc/run_fsqc`. * After that, all other flags are identical to the ones that are used for the `fsqc` program (which are explained on the main page and the help message of the program). That is, there can be more options than specified in this example command. Note that file- and pathnames need to correspond to the targets of the file / directory mappings within the singularity image, not to the local system. * The `--shape` option is currently not supported for running in conjunction with Docker or Singularity, since it relies on the FreeSurfer software package, which is not included in our Docker or Singularity images.