From ce3481152666d5bb328a8d027bdf03a9c306a9a6 Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Sun, 29 Sep 2024 10:51:52 +0200 Subject: [PATCH 1/6] package and lint --- .dockerignore | 1 + .gitignore | 2 + .pre-commit-config.yaml | 28 +++++----- code/_version.py | 1 - pyproject.toml | 50 +++++++++++++----- {code => src/cat12}/.gitattributes | 0 {code => src/cat12}/README.md | 0 .../__pycache__/_version.cpython-312.pyc | Bin 0 -> 178 bytes {code => src/cat12}/_parsers.py | 4 +- {code => src/cat12}/bids_utils.py | 9 ++-- {code => src/cat12}/cat_logging.py | 4 +- .../cat12}/data/methods/template.jinja | 0 {code => src/cat12}/defaults.py | 0 {code => src/cat12}/exit_codes.json | 0 {code => src/cat12}/main.py | 37 ++++++------- {code => src/cat12}/methods.py | 9 ++-- {code => src/cat12}/requirements.txt | 0 {code => src/cat12}/utils.py | 3 +- 18 files changed, 85 insertions(+), 63 deletions(-) delete mode 100644 code/_version.py rename {code => src/cat12}/.gitattributes (100%) rename {code => src/cat12}/README.md (100%) create mode 100644 src/cat12/__pycache__/_version.cpython-312.pyc rename {code => src/cat12}/_parsers.py (97%) rename {code => src/cat12}/bids_utils.py (94%) rename {code => src/cat12}/cat_logging.py (91%) rename {code => src/cat12}/data/methods/template.jinja (100%) rename {code => src/cat12}/defaults.py (100%) rename {code => src/cat12}/exit_codes.json (100%) rename {code => src/cat12}/main.py (92%) rename {code => src/cat12}/methods.py (84%) rename {code => src/cat12}/requirements.txt (100%) rename {code => src/cat12}/utils.py (96%) diff --git a/.dockerignore b/.dockerignore index c8d4e40..20beea6 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,5 +1,6 @@ # ignoring this might speed up build # by preventing passing extra content to the docker daemon +src/cat12/_version.py .simg diff --git a/.gitignore b/.gitignore index f9a32a5..bdc5a20 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +src/cat12/_version.py + .simg tests/data diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6b4d514..ff4d791 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,21 +22,19 @@ repos: hooks: - id: codespell -- repo: https://github.com/pycqa/isort - rev: 5.13.2 +# Format TOML files +- repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks + rev: v2.14.0 hooks: - - id: isort - args: [--line-length, '79', --profile, black] + - id: pretty-format-toml + args: [--autofix, --indent, '4'] -- repo: https://github.com/psf/black-pre-commit-mirror - rev: 24.8.0 +- repo: https://github.com/astral-sh/ruff-pre-commit + # Ruff version. + rev: v0.6.8 hooks: - - id: black - args: [--line-length, '79'] - -- repo: https://github.com/pycqa/flake8 - rev: 7.1.1 - hooks: - - id: flake8 - args: [--config, .flake8, --verbose] - additional_dependencies: [flake8-docstrings, flake8-use-fstring, flake8-functions, flake8-bugbear] + # Run the linter. + - id: ruff + args: [--fix] + # Run the formatter. + - id: ruff-format diff --git a/code/_version.py b/code/_version.py deleted file mode 100644 index 3dc1f76..0000000 --- a/code/_version.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = "0.1.0" diff --git a/pyproject.toml b/pyproject.toml index 2bf3137..ad16240 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,11 +1,11 @@ [build-system] build-backend = "hatchling.build" -requires = ["hatchling"] +requires = ["hatchling", "hatch-vcs"] [project] -version = "0.1.0" dependencies = ["rich_argparse", "pybids", "nibabel"] -license = { text = "MIT" } +dynamic = ["version"] +license = {text = "MIT"} name = "cat12" readme = "README.md" requires-python = ">=3.9" @@ -17,20 +17,44 @@ docs = [ "sphinx-argparse", "sphinx-copybutton", "pydata-sphinx-theme", - "sphinx-togglebutton", + "sphinx-togglebutton" ] +[tool.codespell] +builtin = "clear,rare" + +[tool.hatch.build.hooks.vcs] +version-file = "src/cat12/_version.py" + [tool.hatch.build.targets.wheel] -packages = ["code"] +packages = ["src/cat12"] + +[tool.hatch.version] +source = "vcs" -[tool.black] +[tool.ruff] +include = ["pyproject.toml", "src/**/*.py", "scripts/**/*.py"] +indent-width = 4 line-length = 79 +target-version = "py39" -[tool.codespell] -builtin = "clear,rare" +[tool.ruff.format] +docstring-code-format = true +docstring-code-line-length = "dynamic" +indent-style = "space" +line-ending = "auto" +quote-style = "double" +skip-magic-trailing-comma = false + +[tool.ruff.lint] +dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" +fixable = ["ALL"] +ignore = [] +select = ["E", "F", "W", "C90", "I", "D", "B", "UP", "N", "ARG", "PTH", "FLY", "RUF"] +unfixable = [] + +[tool.ruff.lint.mccabe] +max-complexity = 20 -[tool.isort] -combine_as_imports = true -line_length = 79 -profile = "black" -skip_gitignore = true +[tool.ruff.lint.pydocstyle] +convention = "numpy" diff --git a/code/.gitattributes b/src/cat12/.gitattributes similarity index 100% rename from code/.gitattributes rename to src/cat12/.gitattributes diff --git a/code/README.md b/src/cat12/README.md similarity index 100% rename from code/README.md rename to src/cat12/README.md diff --git a/src/cat12/__pycache__/_version.cpython-312.pyc b/src/cat12/__pycache__/_version.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bc1bdb51568afd6bedff1652524c49a6a69eeeea GIT binary patch literal 178 zcmX@j%ge<81c6}>)5L)EV-N=h7@>^M96-i&h7^VEfLs7)6fE2T literal 0 HcmV?d00001 diff --git a/code/_parsers.py b/src/cat12/_parsers.py similarity index 97% rename from code/_parsers.py rename to src/cat12/_parsers.py index 44683d3..4ce24aa 100644 --- a/code/_parsers.py +++ b/src/cat12/_parsers.py @@ -2,8 +2,8 @@ from argparse import ArgumentParser, HelpFormatter -from _version import __version__ -from defaults import CAT_VERSION, MCR_VERSION, supported_batches +from cat12._version import __version__ +from cat12.defaults import CAT_VERSION, MCR_VERSION, supported_batches def _base_parser( diff --git a/code/bids_utils.py b/src/cat12/bids_utils.py similarity index 94% rename from code/bids_utils.py rename to src/cat12/bids_utils.py index f86ee53..b9fc360 100644 --- a/code/bids_utils.py +++ b/src/cat12/bids_utils.py @@ -6,10 +6,11 @@ from pathlib import Path from typing import Any -from _version import __version__ from bids import BIDSLayout # type: ignore -from cat_logging import cat12_log -from utils import create_dir_if_absent + +from cat12._version import __version__ +from cat12.cat_logging import cat12_log +from cat12.utils import create_dir_if_absent logger = cat12_log(name="cat12") @@ -125,5 +126,5 @@ def write_dataset_description(output_dir) -> None: output_file = output_dir / "dataset_description.json" - with open(output_file, "w") as ff: + with Path.open(output_file, "w") as ff: json.dump(data, ff, indent=4) diff --git a/code/cat_logging.py b/src/cat12/cat_logging.py similarity index 91% rename from code/cat_logging.py rename to src/cat12/cat_logging.py index ce4fb9a..4f31b79 100644 --- a/code/cat_logging.py +++ b/src/cat12/cat_logging.py @@ -7,14 +7,14 @@ from rich.logging import RichHandler from rich.traceback import install +FORMAT = "cat12 - %(asctime)s - %(message)s" + def cat12_log(name: str | None = None) -> logging.Logger: """Create log.""" # let rich print the traceback install(show_locals=True) - FORMAT = "cat12 - %(asctime)s - %(message)s" - if not name: name = "rich" diff --git a/code/data/methods/template.jinja b/src/cat12/data/methods/template.jinja similarity index 100% rename from code/data/methods/template.jinja rename to src/cat12/data/methods/template.jinja diff --git a/code/defaults.py b/src/cat12/defaults.py similarity index 100% rename from code/defaults.py rename to src/cat12/defaults.py diff --git a/code/exit_codes.json b/src/cat12/exit_codes.json similarity index 100% rename from code/exit_codes.json rename to src/cat12/exit_codes.json diff --git a/code/main.py b/src/cat12/main.py similarity index 92% rename from code/main.py rename to src/cat12/main.py index 3136723..19dc1fc 100644 --- a/code/main.py +++ b/src/cat12/main.py @@ -10,26 +10,27 @@ from subprocess import PIPE, STDOUT, Popen import nibabel as nib -from _parsers import common_parser -from _version import __version__ -from bids_utils import ( +from rich import print +from rich_argparse import RichHelpFormatter + +from cat12._parsers import common_parser +from cat12._version import __version__ +from cat12.bids_utils import ( get_dataset_layout, init_derivatives_layout, list_subjects, ) -from cat_logging import cat12_log -from defaults import log_levels -from methods import generate_method_section -from rich import print -from rich_argparse import RichHelpFormatter -from utils import progress_bar +from cat12.cat_logging import cat12_log +from cat12.defaults import log_levels +from cat12.methods import generate_method_section +from cat12.utils import progress_bar env = os.environ env["PYTHONUNBUFFERED"] = "True" argv = sys.argv -with open(Path(__file__).parent / "exit_codes.json") as f: +with Path.open(Path(__file__).parent / "exit_codes.json") as f: EXIT_CODES = json.load(f) # Get environment variable @@ -70,7 +71,7 @@ def main(): files = [STANDALONE / f"cat_standalone_{target}.m"] for source_file in files: - logger.info(f"Copying {source_file} to {str(output_dir)}") + logger.info(f"Copying {source_file} to {output_dir!s}") shutil.copy(source_file, output_dir) sys.exit(EXIT_CODES["SUCCESS"]["Value"]) @@ -104,7 +105,6 @@ def main(): sys.exit(EXIT_CODES["FAILURE"]["Value"]) if command == "segment": - segment_type = args.type if isinstance(segment_type, list): segment_type = segment_type[0] @@ -115,8 +115,7 @@ def main(): copy_files(layout_in, output_dir, subjects) layout_out = init_derivatives_layout(output_dir) else: - OUTPUT_DIR = os.path.relpath(output_dir, bids_dir) - os.environ["OUTPUT_DIR"] = OUTPUT_DIR + os.environ["OUTPUT_DIR"] = os.path.relpath(output_dir, bids_dir) layout_out = layout_in batch = define_batch(segment_type=segment_type) @@ -136,13 +135,11 @@ def main(): text = "processing subjects" with progress_bar(text=text) as progress: - subject_loop = progress.add_task( description="processing subjects", total=len(subjects) ) for subject_label in subjects: - this_filter = { "datatype": "anat", "suffix": "T1w", @@ -190,10 +187,8 @@ def check_input(subject_label: str, bf: list, segment_type: str): return False if is_longitudinal_segmentation(segment_type) and len(bf) < 2: logger.warning( - ( - "Longitudinal segmentation requested " - f"but subject {subject_label} only has 1 image." - ) + "Longitudinal segmentation requested " + f"but subject {subject_label} only has 1 image." ) return True @@ -282,7 +277,7 @@ def copy_files(layout_in, output_dir, subjects): output_filename.parent.mkdir(exist_ok=True, parents=True) - logger.info(f"Copying {file.path} to {str(output_dir)}") + logger.info(f"Copying {file.path} to {output_dir!s}") img = nib.load(file.path) nib.save(img, output_filename) diff --git a/code/methods.py b/src/cat12/methods.py similarity index 84% rename from code/methods.py rename to src/cat12/methods.py index 51f3bdf..bf97712 100644 --- a/code/methods.py +++ b/src/cat12/methods.py @@ -2,17 +2,18 @@ from pathlib import Path -from _version import __version__ -from defaults import CAT_VERSION, MCR_VERSION from jinja2 import Environment, FileSystemLoader, select_autoescape +from cat12._version import __version__ +from cat12.defaults import CAT_VERSION, MCR_VERSION + def generate_method_section( output_dir: Path, version: str = __version__, cat_version: str = CAT_VERSION, mcr_version: str = MCR_VERSION, - batch: str = None, + batch: str | None = None, ) -> None: """Add a method section to the output dataset.""" env = Environment( @@ -34,5 +35,5 @@ def generate_method_section( "batch": batch, } - with open(output_file, "w") as f: + with Path.open(output_file, "w") as f: print(template.render(data=data), file=f) diff --git a/code/requirements.txt b/src/cat12/requirements.txt similarity index 100% rename from code/requirements.txt rename to src/cat12/requirements.txt diff --git a/code/utils.py b/src/cat12/utils.py similarity index 96% rename from code/utils.py rename to src/cat12/utils.py index 06ad82c..4157354 100644 --- a/code/utils.py +++ b/src/cat12/utils.py @@ -4,7 +4,6 @@ from pathlib import Path -from cat_logging import cat12_log from rich.progress import ( BarColumn, MofNCompleteColumn, @@ -16,6 +15,8 @@ TimeRemainingColumn, ) +from cat12.cat_logging import cat12_log + logger = cat12_log(name="cat12") From 3dbb76f7743fe4e4b18195dfe43d872d0049f678 Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Sun, 29 Sep 2024 11:14:41 +0200 Subject: [PATCH 2/6] fixes --- .dockerignore | 165 +++++++++++++++++- .flake8 | 5 - .gitignore | 165 +++++++++++++++++- CHANGELOG.md | 12 ++ Dockerfile | 10 +- pyproject.toml | 10 +- requirements.txt | 101 +++++++++++ src/cat12/.gitattributes | 1 - .../__pycache__/_version.cpython-312.pyc | Bin 178 -> 0 bytes src/cat12/requirements.txt | 4 - tox.ini | 33 ++++ 11 files changed, 487 insertions(+), 19 deletions(-) delete mode 100644 .flake8 create mode 100644 requirements.txt delete mode 100644 src/cat12/.gitattributes delete mode 100644 src/cat12/__pycache__/_version.cpython-312.pyc delete mode 100644 src/cat12/requirements.txt create mode 100644 tox.ini diff --git a/.dockerignore b/.dockerignore index 20beea6..0ebb2f9 100644 --- a/.dockerignore +++ b/.dockerignore @@ -6,8 +6,169 @@ src/cat12/_version.py tests/data -env - tmp docs/build + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# 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/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/latest/usage/project/#working-with-version-control +.pdm.toml +.pdm-python +.pdm-build/ + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ diff --git a/.flake8 b/.flake8 deleted file mode 100644 index 890a732..0000000 --- a/.flake8 +++ /dev/null @@ -1,5 +0,0 @@ -[flake8] -count = True -show-source = True -statistics = True -max_function_length = 200 diff --git a/.gitignore b/.gitignore index bdc5a20..00baece 100644 --- a/.gitignore +++ b/.gitignore @@ -4,8 +4,169 @@ src/cat12/_version.py tests/data -env - tmp docs/build + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# 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/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/latest/usage/project/#working-with-version-control +.pdm.toml +.pdm-python +.pdm-build/ + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ diff --git a/CHANGELOG.md b/CHANGELOG.md index f94d986..09a4bba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,3 +20,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 --> ## [Unreleased] + +### Added + +### Changed + +### Deprecated + +### Removed + +### Fixed + +### Security diff --git a/Dockerfile b/Dockerfile index c8e3062..0a6ecef 100644 --- a/Dockerfile +++ b/Dockerfile @@ -71,10 +71,12 @@ RUN curl -fsSL https://deno.land/install.sh | sh && \ # transfer code and set permission RUN mkdir -p /code -COPY ./code/requirements.txt /code -RUN pip install -r /code/requirements.txt +COPY . /code +RUN cd code && \ + git restore . \ + pip install -r /code/requirements.txt && \ + pip install . -COPY ./code /code RUN ls /code && find /code -type f -print0 | xargs -0 chmod +r # modify enigma script to output content to path defined by an env variable @@ -82,4 +84,4 @@ RUN sed -i -e "s/cat_version/getenv('OUTPUT_DIR')/g" /opt/CAT12${CAT_VERSION}/st WORKDIR ${STANDALONE} -ENTRYPOINT ["python3", "/code/main.py"] +ENTRYPOINT ["cat12"] diff --git a/pyproject.toml b/pyproject.toml index ad16240..21f0c77 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,12 @@ build-backend = "hatchling.build" requires = ["hatchling", "hatch-vcs"] [project] -dependencies = ["rich_argparse", "pybids", "nibabel"] +dependencies = [ + "rich_argparse", + "pybids", + "nibabel", + "jinja2" +] dynamic = ["version"] license = {text = "MIT"} name = "cat12" @@ -20,6 +25,9 @@ docs = [ "sphinx-togglebutton" ] +[project.scripts] +cat12 = "cat12.main:main" + [tool.codespell] builtin = "clear,rare" diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..3b16311 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,101 @@ +# +# This file is autogenerated by pip-compile with Python 3.12 +# by the following command: +# +# pip-compile --strip-extras pyproject.toml +# +astor==0.8.1 + # via formulaic +attrs==24.2.0 + # via + # jsonschema + # referencing +bids-validator==1.14.7.post0 + # via pybids +bidsschematools==0.11.3 + # via bids-validator +click==8.1.7 + # via + # bidsschematools + # pybids +docopt==0.6.2 + # via num2words +formulaic==0.5.2 + # via pybids +fsspec==2024.9.0 + # via universal-pathlib +greenlet==3.1.1 + # via sqlalchemy +interface-meta==1.3.0 + # via formulaic +jinja2==3.1.4 + # via cat12 (pyproject.toml) +jsonschema==4.23.0 + # via bidsschematools +jsonschema-specifications==2023.12.1 + # via jsonschema +markdown-it-py==3.0.0 + # via rich +markupsafe==2.1.5 + # via jinja2 +mdurl==0.1.2 + # via markdown-it-py +nibabel==5.2.1 + # via + # cat12 (pyproject.toml) + # pybids +num2words==0.5.13 + # via pybids +numpy==2.1.1 + # via + # formulaic + # nibabel + # pandas + # pybids + # scipy +packaging==24.1 + # via nibabel +pandas==2.2.3 + # via + # formulaic + # pybids +pybids==0.17.2 + # via cat12 (pyproject.toml) +pygments==2.18.0 + # via rich +python-dateutil==2.9.0.post0 + # via pandas +pytz==2024.2 + # via pandas +pyyaml==6.0.2 + # via bidsschematools +referencing==0.35.1 + # via + # jsonschema + # jsonschema-specifications +rich==13.8.1 + # via rich-argparse +rich-argparse==1.5.2 + # via cat12 (pyproject.toml) +rpds-py==0.20.0 + # via + # jsonschema + # referencing +scipy==1.14.1 + # via + # formulaic + # pybids +six==1.16.0 + # via python-dateutil +sqlalchemy==2.0.35 + # via pybids +typing-extensions==4.12.2 + # via + # formulaic + # sqlalchemy +tzdata==2024.2 + # via pandas +universal-pathlib==0.2.5 + # via pybids +wrapt==1.16.0 + # via formulaic diff --git a/src/cat12/.gitattributes b/src/cat12/.gitattributes deleted file mode 100644 index 8bb0167..0000000 --- a/src/cat12/.gitattributes +++ /dev/null @@ -1 +0,0 @@ -* annex.largefiles=nothing diff --git a/src/cat12/__pycache__/_version.cpython-312.pyc b/src/cat12/__pycache__/_version.cpython-312.pyc deleted file mode 100644 index bc1bdb51568afd6bedff1652524c49a6a69eeeea..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 178 zcmX@j%ge<81c6}>)5L)EV-N=h7@>^M96-i&h7^VEfLs7)6fE2T diff --git a/src/cat12/requirements.txt b/src/cat12/requirements.txt deleted file mode 100644 index e7f42b7..0000000 --- a/src/cat12/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -rich_argparse -pybids -nibabel -jinja2 diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..3467b86 --- /dev/null +++ b/tox.ini @@ -0,0 +1,33 @@ +; See https://tox.wiki/en +[tox] +requires = + tox>=4 +; run lint by default when just calling "tox" +env_list = lint + +; ENVIRONMENTS +; ------------ +[style] +description = common environment for style checkers (rely on pre-commit hooks) +skip_install = true +deps = + pre-commit + +; COMMANDS +; -------- +[testenv:lint] +description = install pre-commit hooks and run all linters and formatters +skip_install = true +deps = + {[style]deps} +commands = + pre-commit install + pre-commit run --all-files --show-diff-on-failure {posargs:} + +[testenv:update_dependencies] +description = update requirements.txt +skip_install = true +deps = + pip-tools +commands = + pip-compile --strip-extras pyproject.toml {posargs:} From a9f99ddc9ba4d280a1a5ff7a6c99ba4591191cb0 Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Sun, 29 Sep 2024 11:16:07 +0200 Subject: [PATCH 3/6] fixes --- Dockerfile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 0a6ecef..8038d7d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -22,8 +22,7 @@ ENV SPMROOT="/opt/CAT12${CAT_VERSION}" \ PATH="$DENO_INSTALL/bin:/opt/CAT12${CAT_VERSION}:$PATH" \ STANDALONE="/opt/CAT12${CAT_VERSION}/standalone" -RUN export ND_ENTRYPOINT="/neurodocker/startup.sh" \ - && apt-get update -qq \ +RUN apt-get update -qq \ && apt-get install -y -q --no-install-recommends \ apt-utils \ bc \ @@ -31,6 +30,7 @@ RUN export ND_ENTRYPOINT="/neurodocker/startup.sh" \ ca-certificates \ curl \ dbus-x11 \ + git \ libncurses5 \ libxext6 \ libxmu6 \ @@ -73,8 +73,8 @@ RUN curl -fsSL https://deno.land/install.sh | sh && \ RUN mkdir -p /code COPY . /code RUN cd code && \ - git restore . \ - pip install -r /code/requirements.txt && \ + git restore . && \ + pip install -r requirements.txt && \ pip install . RUN ls /code && find /code -type f -print0 | xargs -0 chmod +r From fad2fdb1ff5f7dd4f42425ee04f47576990bd266 Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Sun, 29 Sep 2024 11:45:32 +0200 Subject: [PATCH 4/6] fix CLI --- src/cat12/defaults.py | 8 +++++++- src/cat12/main.py | 7 +++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/cat12/defaults.py b/src/cat12/defaults.py index 9e58f1e..adc1d2b 100644 --- a/src/cat12/defaults.py +++ b/src/cat12/defaults.py @@ -5,7 +5,13 @@ import os MCR_VERSION = os.getenv("MCR_VERSION") -CAT_VERSION = " ".join(os.getenv("CAT_VERSION")[1:10].split("_")) +if MCR_VERSION is None: + MCR_VERSION = "2017b" + +tmp = os.getenv("CAT_VERSION") +if tmp is None: + tmp = f".8.1_r2042_R${MCR_VERSION}" +CAT_VERSION = " ".join(tmp[1:10].split("_")) def log_levels() -> list[str]: diff --git a/src/cat12/main.py b/src/cat12/main.py index 19dc1fc..e0611e2 100644 --- a/src/cat12/main.py +++ b/src/cat12/main.py @@ -21,7 +21,7 @@ list_subjects, ) from cat12.cat_logging import cat12_log -from cat12.defaults import log_levels +from cat12.defaults import CAT_VERSION, log_levels from cat12.methods import generate_method_section from cat12.utils import progress_bar @@ -34,7 +34,10 @@ EXIT_CODES = json.load(f) # Get environment variable -STANDALONE = Path(os.getenv("STANDALONE")) +STANDALONE = os.getenv("STANDALONE") +if STANDALONE is None: + STANDALONE = f"/opt/CAT12${CAT_VERSION}/standalone" +STANDALONE = Path(STANDALONE) logger = cat12_log(name="cat12") From 6a5866b5fb8f558ca20308ad5e7de81fe0d2b7dd Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Sun, 29 Sep 2024 11:53:34 +0200 Subject: [PATCH 5/6] try pushing to dockerhub --- .github/workflows/docker.yml | 52 +++++++++++++++++++++++++++++------- 1 file changed, 43 insertions(+), 9 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index a0b4fd0..af7d989 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -23,15 +23,6 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 - # # cache tarred docker image to speed up build on follow up runs - # - uses: actions/cache@v4 - # id: cache - # with: - # path: ${{ env.IMAGE }}/image.tar - # key: data - # - if: ${{ steps.cache.outputs.cache-hit != 'true' }} - # name: Load image - # run: docker load -i ${{ env.IMAGE }}/image.tar - name: Build the Docker image run: | docker build . --tag ${{env.USER_NAME}}/${{env.REPO_NAME}} @@ -137,3 +128,46 @@ jobs: with: name: output_${{ matrix.type }} path: /home/runner/work/cat12-container/cat12-container/tests/data/ds002799/derivatives + + docker-push: + runs-on: ubuntu-latest + needs: [one-session] + defaults: + run: + shell: bash -el {0} + if: ${{ github.ref == 'refs/heads/main' || github.ref_type == 'tag' }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Restore docker image + uses: actions/download-artifact@v4 + with: + name: docker + path: ${{ env.IMAGE }} + - name: Log in to Docker Hub + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Load image + run: docker load -i ${{ env.IMAGE }}/image.tar + - name: Push unstable to dockerhub on tags or on main + run: | + echo "Pushing unstable versions to DockerHub" + unstable="${{env.USER_NAME}}/${{env.REPO_NAME}}:unstable" + docker tag "${{env.USER_NAME}}/${{env.REPO_NAME}}" "${unstable}" + docker push "${unstable}" + - name: Push stable release to dockerhub on tags only + if: ${{ github.ref_type == 'tag' }} + run: | + echo "Pushing stable and latest versions to DockerHub for latest and ${{ github.ref_name }}" + + unstable="${{env.USER_NAME}}/${{env.REPO_NAME}}:unstable" + latest="${{env.USER_NAME}}/${{env.REPO_NAME}}:latest" + docker tag "${unstable}" "${latest}" + docker push "${latest}" + + tagged_release="${{env.USER_NAME}}/${{env.REPO_NAME}}:${{ github.ref_name }}" + docker tag "${unstable}" "${tagged_release}" + docker push "${tagged_release}" From 7b84f6b27226a483b5617e9c476027419bf573d9 Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Sun, 29 Sep 2024 12:24:13 +0200 Subject: [PATCH 6/6] apply sourcery --- src/cat12/bids_utils.py | 33 ++++++++++++++++----------------- src/cat12/main.py | 26 +++++++++++++------------- 2 files changed, 29 insertions(+), 30 deletions(-) diff --git a/src/cat12/bids_utils.py b/src/cat12/bids_utils.py index b9fc360..66b46e0 100644 --- a/src/cat12/bids_utils.py +++ b/src/cat12/bids_utils.py @@ -106,24 +106,23 @@ def write_dataset_description(output_dir) -> None: "DatasetType": "derivative", "License": "???", "ReferencesAndLinks": ["https://doi.org/10.1101/2022.06.11.495736"], + "GeneratedBy": [ + { + "Name": "cat12", + "Version": __version__, + "Container": {"Type": "", "Tag": __version__}, + "Description": "", + "CodeURL": "", + } + ], + "SourceDatasets": [ + { + "DOI": "doi:", + "URL": "", + "Version": "", + } + ], } - data["GeneratedBy"] = [ - { - "Name": "cat12", - "Version": __version__, - "Container": {"Type": "", "Tag": __version__}, - "Description": "", - "CodeURL": "", - }, - ] - data["SourceDatasets"] = [ - { - "DOI": "doi:", - "URL": "", - "Version": "", - } - ] - output_file = output_dir / "dataset_description.json" with Path.open(output_file, "w") as ff: diff --git a/src/cat12/main.py b/src/cat12/main.py index e0611e2..14560b6 100644 --- a/src/cat12/main.py +++ b/src/cat12/main.py @@ -59,26 +59,26 @@ def main(): command = args.command - if command == "help": - subprocess.run([STANDALONE / "cat_standalone.sh"]) - sys.exit(EXIT_CODES["SUCCESS"]["Value"]) - - elif command == "copy": + if command == "copy": target = args.target[0] output_dir.mkdir(exist_ok=True, parents=True) - if target == "all": - files = STANDALONE.glob("*.m") - else: - files = [STANDALONE / f"cat_standalone_{target}.m"] - + files = ( + STANDALONE.glob("*.m") + if target == "all" + else [STANDALONE / f"cat_standalone_{target}.m"] + ) for source_file in files: logger.info(f"Copying {source_file} to {output_dir!s}") shutil.copy(source_file, output_dir) sys.exit(EXIT_CODES["SUCCESS"]["Value"]) + elif command == "help": + subprocess.run([STANDALONE / "cat_standalone.sh"]) + sys.exit(EXIT_CODES["SUCCESS"]["Value"]) + elif command == "view": target = args.target[0] source_file = STANDALONE / f"cat_standalone_{target}.m" @@ -185,7 +185,7 @@ def main(): def check_input(subject_label: str, bf: list, segment_type: str): """Check number of input files.""" - if len(bf) < 1: + if not bf: logger.warning(f"No data found for subject {subject_label}.") return False if is_longitudinal_segmentation(segment_type) and len(bf) < 2: @@ -298,10 +298,10 @@ def run_validation(bids_dir): def gunzip_all_niftis(output_dir: Path, subject_label: str): """Gunzip all niftis for a subject.""" logger.info(f"Gunzipping files for {subject_label}") - files = [x for x in (output_dir / f"sub-{subject_label}").glob("**/*.nii")] + files = list((output_dir / f"sub-{subject_label}").glob("**/*.nii")) for f in files: nii = nib.load(f) - nii.to_filename(str(f) + ".gz") + nii.to_filename(f"{f!s}.gz") f.unlink()