From b55e342b6011a6074029d0da88374444b4ba25bb Mon Sep 17 00:00:00 2001 From: diersk Date: Mon, 2 Mar 2026 11:55:42 +0100 Subject: [PATCH 01/16] Update to headless whippersnappy --- docker/Dockerfile | 5 ++--- fsqc/createSurfacePlots.py | 2 +- requirements.txt | 2 +- singularity/Singularity.md | 4 ++-- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 4ae358d..35e04e1 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -17,9 +17,8 @@ RUN apt-get install -y --no-install-recommends \ python3-setuptools \ python3-wheel \ libxcb-xinerama0 \ - xvfb \ - libopengl0 \ - libegl1-mesa + libosmesa6 \ + libgl1 # clone fsqc RUN git clone https://github.com/Deep-MI/fsqc.git /app/fsqc diff --git a/fsqc/createSurfacePlots.py b/fsqc/createSurfacePlots.py index 0be5780..0bcc2e3 100644 --- a/fsqc/createSurfacePlots.py +++ b/fsqc/createSurfacePlots.py @@ -31,7 +31,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 # ----------------------------------------------------------------------------- # import surfaces and overlays diff --git a/requirements.txt b/requirements.txt index 184b5ce..5e7d021 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..f5443f9 100644 --- a/singularity/Singularity.md +++ b/singularity/Singularity.md @@ -28,13 +28,13 @@ singularity exec \ -B /path/to/subjects/directory:/data \ -B /path/to/subjects/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 to want 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. From ac295379e308bbcc6d34ee3b5a7eb8ed5647ed71 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Mar 2026 14:42:37 +0000 Subject: [PATCH 02/16] Fix grammatical typo in Singularity.md Co-authored-by: kdiers <24712358+kdiers@users.noreply.github.com> --- singularity/Singularity.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/singularity/Singularity.md b/singularity/Singularity.md index f5443f9..580be8f 100644 --- a/singularity/Singularity.md +++ b/singularity/Singularity.md @@ -35,6 +35,6 @@ singularity exec \ * 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`. +* 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. From 7c05a5a354c66525709f848595c3a9aa44770c7b Mon Sep 17 00:00:00 2001 From: Martin Reuter Date: Tue, 3 Mar 2026 15:34:34 +0100 Subject: [PATCH 03/16] Apply suggestion from @m-reuter --- fsqc/createSurfacePlots.py | 1 - 1 file changed, 1 deletion(-) diff --git a/fsqc/createSurfacePlots.py b/fsqc/createSurfacePlots.py index 0bcc2e3..858c774 100644 --- a/fsqc/createSurfacePlots.py +++ b/fsqc/createSurfacePlots.py @@ -31,7 +31,6 @@ def createSurfacePlots(SUBJECT, SUBJECTS_DIR, SURFACES_OUTDIR, VIEWS, FASTSURFER import os from whippersnappy import snap1 - from whippersnappy import ViewType # ----------------------------------------------------------------------------- # import surfaces and overlays From f3031a504aac93615d61e0e10c6f8a7f7e913633 Mon Sep 17 00:00:00 2001 From: Martin Reuter Date: Tue, 3 Mar 2026 15:34:42 +0100 Subject: [PATCH 04/16] Apply suggestion from @m-reuter --- fsqc/createSurfacePlots.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fsqc/createSurfacePlots.py b/fsqc/createSurfacePlots.py index 858c774..4bb80eb 100644 --- a/fsqc/createSurfacePlots.py +++ b/fsqc/createSurfacePlots.py @@ -30,7 +30,7 @@ def createSurfacePlots(SUBJECT, SUBJECTS_DIR, SURFACES_OUTDIR, VIEWS, FASTSURFER import os - from whippersnappy import snap1 + from whippersnappy import snap1, ViewType # ----------------------------------------------------------------------------- # import surfaces and overlays From 706c493e610ebd4c7d74577e3db4f4d27173e92a Mon Sep 17 00:00:00 2001 From: Martin Reuter Date: Tue, 3 Mar 2026 15:35:00 +0100 Subject: [PATCH 05/16] Apply suggestion from @m-reuter --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 5e7d021..270de9c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,4 +12,4 @@ glfw pillow pyrr PyQt6 -whippersnappy==2.1.0 +whippersnappy>=2.1.0 From 38e35c215c57c510819100ea5132bc6f7d494466 Mon Sep 17 00:00:00 2001 From: diersk Date: Tue, 10 Mar 2026 15:11:19 +0100 Subject: [PATCH 06/16] Update docker --- .dockerignore | 74 +++++++++++++++++++++++++++++++++++++++++++++++ .flake8 | 23 +++++++++++++++ .gitignore | 28 +++++++++++------- docker/Docker.md | 8 +++-- docker/Dockerfile | 65 ++++++++++++++++++++++++----------------- 5 files changed, 158 insertions(+), 40 deletions(-) create mode 100644 .dockerignore create mode 100644 .flake8 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/.flake8 b/.flake8 new file mode 100644 index 0000000..4f14d00 --- /dev/null +++ b/.flake8 @@ -0,0 +1,23 @@ +[flake8] +max-line-length = 88 + +ignore = + # these rules don't play well with black + # whitespace before ':' + E203 + # line break before binary operator + W503 + E241,E305,W504,W605,E731 + +exclude = + .git + .github + setup.py + testing + *.egg-info + +per-file-ignores = + # __init__.py files are allowed to have unused imports and lines-too-long + */__init__.py:F401 + */**/__init__.py:F401,E501 + 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/docker/Docker.md b/docker/Docker.md index c283fe3..127bc81 100644 --- a/docker/Docker.md +++ b/docker/Docker.md @@ -6,12 +6,14 @@ 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. diff --git a/docker/Dockerfile b/docker/Dockerfile index 35e04e1..8790fb5 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,44 +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 \ - libosmesa6 \ - libgl1 - -# 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"] From 43047d32596b71c30d93ccdd439becdd2479f53e Mon Sep 17 00:00:00 2001 From: diersk Date: Tue, 10 Mar 2026 15:21:25 +0100 Subject: [PATCH 07/16] Removed support and tests for Python3.9 --- .github/workflows/build.yml | 2 +- README.md | 2 +- fsqc/fsqcMain.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2f50829..3ed496d 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"] name: ${{ matrix.os }} - py${{ matrix.python-version }} runs-on: ${{ matrix.os }}-latest defaults: 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/fsqc/fsqcMain.py b/fsqc/fsqcMain.py index c90c48e..00ad7f5 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 From 0ef7987b266eea54e39ef94577988875902a08f5 Mon Sep 17 00:00:00 2001 From: diersk Date: Tue, 10 Mar 2026 15:23:38 +0100 Subject: [PATCH 08/16] Formatting --- fsqc/createSurfacePlots.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fsqc/createSurfacePlots.py b/fsqc/createSurfacePlots.py index 4bb80eb..a6fc70c 100644 --- a/fsqc/createSurfacePlots.py +++ b/fsqc/createSurfacePlots.py @@ -30,7 +30,7 @@ def createSurfacePlots(SUBJECT, SUBJECTS_DIR, SURFACES_OUTDIR, VIEWS, FASTSURFER import os - from whippersnappy import snap1, ViewType + from whippersnappy import ViewType, snap1 # ----------------------------------------------------------------------------- # import surfaces and overlays From 61750bda7c5f74dd44d24f56348354dd120262aa Mon Sep 17 00:00:00 2001 From: Kersten Diers Date: Tue, 10 Mar 2026 15:46:16 +0100 Subject: [PATCH 09/16] Update singularity/Singularity.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- singularity/Singularity.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/singularity/Singularity.md b/singularity/Singularity.md index 580be8f..5e57624 100644 --- a/singularity/Singularity.md +++ b/singularity/Singularity.md @@ -26,7 +26,7 @@ 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 \ /app/fsqc/run_fsqc \ --subjects_dir /data \ From dba330ae97d888a236a6b9c9c29ea202588792ba Mon Sep 17 00:00:00 2001 From: diersk Date: Tue, 10 Mar 2026 15:49:31 +0100 Subject: [PATCH 10/16] Updated documentation --- docker/Docker.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/Docker.md b/docker/Docker.md index 127bc81..cb0046a 100644 --- a/docker/Docker.md +++ b/docker/Docker.md @@ -17,7 +17,7 @@ The name of the image will be `deepmi/fsqcdocker`, and it will be built from the 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 From da7bd37eeb5138ea8ecf0f0f2f42253e7b498fe0 Mon Sep 17 00:00:00 2001 From: diersk Date: Tue, 10 Mar 2026 15:49:59 +0100 Subject: [PATCH 11/16] Removed support for Python3.9 --- pyproject.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 289aef8..8de0619 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,7 +25,6 @@ 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', From 0329777e8149b8d2ac2f3049c3fb269ab2c69833 Mon Sep 17 00:00:00 2001 From: diersk Date: Wed, 11 Mar 2026 10:31:43 +0100 Subject: [PATCH 12/16] Applied ruff changes --- fsqc/createScreenshots.py | 2 +- fsqc/fsqcMain.py | 12 ++++--- fsqc/outlierDetection.py | 72 +++++++++++++++++++-------------------- fsqc/utils/_config.py | 5 +-- 4 files changed, 48 insertions(+), 43 deletions(-) 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/fsqcMain.py b/fsqc/fsqcMain.py index 00ad7f5..525037b 100644 --- a/fsqc/fsqcMain.py +++ b/fsqc/fsqcMain.py @@ -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 From ecb02cc4211e59b14eb813e12e5cb413388e10ba Mon Sep 17 00:00:00 2001 From: diersk Date: Wed, 11 Mar 2026 14:00:10 +0100 Subject: [PATCH 13/16] Updated github workflows --- .github/workflows/build.yml | 2 +- .github/workflows/code-style.yml | 9 ++------- .github/workflows/doc.yml | 4 ++-- .github/workflows/publish.yml | 4 ++-- 4 files changed, 7 insertions(+), 12 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3ed496d..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.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: | From 6a176ed797adb43386b36e15fe1a9a7622f9b24a Mon Sep 17 00:00:00 2001 From: diersk Date: Wed, 11 Mar 2026 14:01:26 +0100 Subject: [PATCH 14/16] Support for Python 3.13 and added codespell configuration --- pyproject.toml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 8de0619..400c832 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,6 +28,7 @@ classifiers = [ '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', @@ -115,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 = [ @@ -124,7 +124,6 @@ select = [ "UP", # pyupgrade "B", # flake8-bugbear "I", # isort - # "SIM", # flake8-simplify ] [tool.ruff.lint.per-file-ignores] @@ -140,6 +139,12 @@ addopts = [ "--verbose", ] +[tool.codespell] +ignore-words-list = '' +check-filenames = true +check-hidden = true +skip = './.git,./build,./.mypy_cache,./.pytest_cache' + [tool.coverage.run] branch = true cover_pylib = false From d861f557e50a15481c7f1ab82c0844d576e42f50 Mon Sep 17 00:00:00 2001 From: diersk Date: Wed, 11 Mar 2026 14:01:52 +0100 Subject: [PATCH 15/16] Removed obsolete configuration files --- .codespellignore | 0 .flake8 | 23 ----------------------- 2 files changed, 23 deletions(-) delete mode 100644 .codespellignore delete mode 100644 .flake8 diff --git a/.codespellignore b/.codespellignore deleted file mode 100644 index e69de29..0000000 diff --git a/.flake8 b/.flake8 deleted file mode 100644 index 4f14d00..0000000 --- a/.flake8 +++ /dev/null @@ -1,23 +0,0 @@ -[flake8] -max-line-length = 88 - -ignore = - # these rules don't play well with black - # whitespace before ':' - E203 - # line break before binary operator - W503 - E241,E305,W504,W605,E731 - -exclude = - .git - .github - setup.py - testing - *.egg-info - -per-file-ignores = - # __init__.py files are allowed to have unused imports and lines-too-long - */__init__.py:F401 - */**/__init__.py:F401,E501 - From 860f556e74d41ef624378e04dab212af9cd08240 Mon Sep 17 00:00:00 2001 From: diersk Date: Wed, 11 Mar 2026 14:06:13 +0100 Subject: [PATCH 16/16] Codespell configuration --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 400c832..0413f71 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -140,7 +140,6 @@ addopts = [ ] [tool.codespell] -ignore-words-list = '' check-filenames = true check-hidden = true skip = './.git,./build,./.mypy_cache,./.pytest_cache'