Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions .github/workflows/check_python_compatibility.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
name: Check Python Compatibility

on:
pull_request:
branches: [ main ]
push:
branches: [ main ]

jobs:
test-python-compatibility:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13"]
toolchain: [uv, pip]

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}

- name: Install UV
if: matrix.toolchain == 'uv'
uses: astral-sh/setup-uv@v2
with:
version: "latest"

- name: Build package with ${{ matrix.toolchain }}
if: matrix.toolchain == 'uv'
run: |
uv build
ls -lh dist/

- name: Build package with ${{ matrix.toolchain }}
if: matrix.toolchain == 'pip'
run: |
python -m pip install --upgrade pip build
python -m build
ls -lh dist/

- name: Install package with ${{ matrix.toolchain }}
if: matrix.toolchain == 'uv'
run: |
uv pip install --system dist/*.whl
uv run python -c "import petdeface; print('Package installed successfully')"

- name: Test CLI
if: matrix.toolchain == 'uv'
run: |
uv run petdeface --help

- name: Install package with ${{ matrix.toolchain }}
if: matrix.toolchain == 'pip'
run: |
python -m pip install dist/*.whl
python -c "import petdeface; print('Package installed successfully')"

- name: Test CLI
if: matrix.toolchain == 'pip'
run: |
petdeface --help
26 changes: 20 additions & 6 deletions .github/workflows/petdeface.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,28 @@ jobs:
test_petdeface:
runs-on: ubuntu-latest
name: Test petdeface
strategy:
matrix:
toolchain: [uv, pip]
steps:
- uses: actions/checkout@v3
- name: Install Poetry
run: pipx install poetry
- uses: actions/setup-python@v4
with:
python-version: 3.11
cache: poetry
- run: poetry install
- name: Run All Tests
run: poetry run make testall
- name: Install dependencies (UV)
if: matrix.toolchain == 'uv'
run: |
curl -LsSf https://astral.sh/uv/install.sh | sh
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
uv sync --dev
- name: Install dependencies (pip)
if: matrix.toolchain == 'pip'
run: |
python -m pip install --upgrade pip
pip install .[dev]
- name: Run All Tests (UV)
if: matrix.toolchain == 'uv'
run: uv run make testall
- name: Run All Tests (pip)
if: matrix.toolchain == 'pip'
run: make testall
30 changes: 14 additions & 16 deletions .github/workflows/publish_to_pypi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,23 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

- name: Install dependencies
run: |
pipx install poetry
poetry install

- name: Set up Python
uses: actions/setup-python@v4
- uses: actions/setup-python@v4
with:
python-version: 3.11
cache: 'poetry'
- name: Build Package
run: |
curl -LsSf https://astral.sh/uv/install.sh | sh
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
uv sync --dev
uv build

- name: Build
- name: Check PyPI token
run: |
poetry build
if [ -z "$PYPI_TOKEN_PETDEFACE" ]; then
echo "Error: PYPI_TOKEN_PETDEFACE is not set or is empty check your secrets"
exit 1
fi

- name: Publish
if: ${{ !contains(github.ref_name, 'test') }}
- name: Publish Package
run: |
echo "Publishing to PyPI..."
poetry config pypi-token.pypi $PYPI_TOKEN
poetry publish
uv publish --token $PYPI_TOKEN_PETDEFACE
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@

# ignore virtualenv
venv/
.venv/

# ignore pycache
__pycache__/

# ignore dist
dist/

# ignore egg-info
petdeface.egg-info/

# nipype generates pickle files, we need to redirect where it stores them,
# but until then hack fix
*.pkl
Expand Down
26 changes: 10 additions & 16 deletions .readthedocs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,27 @@
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details

# Required
version: 2

# Set the OS, Python version and other tools you might need
# Build environment
build:
os: ubuntu-22.04
tools:
python: "3.12"
jobs:
post_create_environment:
- pip install poetry
- poetry config virtualenvs.create false
post_install:
- VIRTUAL_ENV=$READTHEDOCS_VIRTUALENV_PATH poetry install --with=dev

# Python environment setup
python:
install:
- method: pip
path: .
extra_requirements:
- dev

# Build documentation in the "docs/" directory with Sphinx
sphinx:
configuration: docs/conf.py
configuration: docs/conf.py

# Optionally build your docs in additional formats such as PDF and ePub
# formats:
# - pdf
# - epub

# Optional but recommended, declare the Python requirements required
# to build your documentation
# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
# python:
# install:
# - requirements: docs/requirements.txt
22 changes: 20 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ This software can be installed via source or via pip from PyPi with `pip install
|---------| ------ |
| `docker build . -t petdeface` | ![docker_build](https://codebuild.us-east-1.amazonaws.com/badges?uuid=eyJlbmNyeXB0ZWREYXRhIjoiYzdXV0tYSkQzTVNkcG04cHA2S055UXlKRlZTU1VONThUMVRoZVcwU3l1aHFhdVBlNDNaRGVCYzdWM1Q0WjYzQ1lRU2ZTSHpmSERPWFRkVXVyb3k3RTZBPSIsIml2UGFyYW1ldGVyU3BlYyI6IjRCZFFIQnNGT2lKcDA1VG4iLCJtYXRlcmlhbFNldFNlcmlhbCI6MX0%3D&branch=main) |
| `docker push` | ![docker push icon](https://codebuild.us-east-1.amazonaws.com/badges?uuid=eyJlbmNyeXB0ZWREYXRhIjoia0c1bEJYUGI2SXlWYi9JMm1tcGtiYWVTdVd3bmlnOUFaTjN4QjJITU5PTVpvQnN3TlowajhxNmhHY2RwQ2Z5SU93OExqc2xvMzFnTHFvajlqVk1MV2FzPSIsIml2UGFyYW1ldGVyU3BlYyI6Ikl6SzRyc1RabzBnSkplTjciLCJtYXRlcmlhbFNldFNlcmlhbCI6MX0%3D&branch=main) |
| `Python 3.14 >= 3.10` | [![Check Python Compatibility](https://github.com/openneuropet/petdeface/actions/workflows/check_python_compatibility.yaml/badge.svg)](https://github.com/openneuropet/petdeface/actions/workflows/check_python_compatibility.yaml) |
| `packaging` | [![Publish to PyPI](https://github.com/openneuropet/petdeface/actions/workflows/publish_to_pypi.yaml/badge.svg)](https://github.com/openneuropet/petdeface/actions/workflows/publish_to_pypi.yaml) |
| `Docs` | ![RTD BADGE](https://app.readthedocs.org/projects/petdeface/badge/?version=latest&style=default) |

## Requirements

Expand Down Expand Up @@ -161,12 +164,27 @@ trouble running this container in singularity/apptainer.

## Development

This project uses poetry to package and build, to create a pip installable version of the package run:
This project supports both [UV](https://github.com/astral-sh/uv) and standard Python (pip + build) workflows for development and packaging.

### Using UV (recommended for speed)

```bash
git clone https://github.com/openneuropet/petdeface.git
cd petdeface
uv build
pip install dist/petdeface-<X.X.X>-py3-none-any.whl # where X.X.X is the version number of the generated file
```

### Using pip and python (no UV required)

```bash
git clone https://github.com/openneuropet/petdeface.git
cd petdeface
poetry build
pip install --upgrade pip
pip install .[dev]
# To build a wheel or sdist:
pip install build
python -m build
pip install dist/petdeface-<X.X.X>-py3-none-any.whl # where X.X.X is the version number of the generated file
```

Expand Down
Binary file not shown.
15 changes: 13 additions & 2 deletions docs/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,22 @@ PETdeface can be installed via PyPi_::

pip install petdeface

Or cloned and installed from source::
Or cloned and installed from source (using UV)::

git clone https://github.com/openneuropet/petdeface.git
cd petdeface
poetry build
uv build
pip install dist/petdeface-<X.X.X>-py3-none-any.whl
# where X.X.X is the version number of the generated file

Or cloned and installed from source (using pip and python)::

git clone https://github.com/openneuropet/petdeface.git
cd petdeface
pip install --upgrade pip
pip install .[dev]
pip install build
python -m build
pip install dist/petdeface-<X.X.X>-py3-none-any.whl
# where X.X.X is the version number of the generated file

Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
8 changes: 4 additions & 4 deletions data/participants.tsv → petdeface/data/participants.tsv
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
participant_id height weight age gender
sub-01 178 58 28 male
sub-mni305 200 65 10 n/a
sub-02 178 58 28 male
participant_id height weight age gender
sub-01 178 58 28 male
sub-mni305 200 65 10 n/a
sub-02 178 58 28 male
Binary file not shown.
28 changes: 17 additions & 11 deletions petdeface/noanat.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,25 @@ def get_data_path(filename: str) -> Path:
FileNotFoundError
If the requested file is not found in the package data
"""
# Get the path to the data directory
data_dir = Path(__file__).parent.parent / "data"
# First try the top-level data directory (for development)
project_root = Path(__file__).parent.parent.parent
top_level_data_dir = project_root / "data"
file_path = top_level_data_dir / filename

# Construct the full path
full_path = data_dir / filename
if file_path.exists():
return file_path
# Fallback: try to find the file in the installed package data
module_dir = Path(__file__).parent
package_data_dir = module_dir / "data"
file_path = package_data_dir / filename

# Check if the file exists
if not full_path.exists():
raise FileNotFoundError(
f"Could not find data file {filename} in data directory"
)
if file_path.exists():
return file_path

return full_path
# If neither location works, raise an error
raise FileNotFoundError(
f"Could not find data file {filename} in package data at {package_data_dir / filename} or {top_level_data_dir / filename}"
)


def get_default_anat(anat) -> Path:
Expand Down Expand Up @@ -302,7 +308,7 @@ def remove_default_anat(
anat_dir = subject_dir / "anat"

# Define the file paths
target_nii = anat_dir / f"sub-{extracted_id}_T1w.nii"
target_nii = anat_dir / f"sub-{extracted_id}_T1w.nii.gz"
target_json = anat_dir / f"sub-{extracted_id}_T1w.json"

# Check if the files exist
Expand Down
Loading