Skip to content

Commit

Permalink
Numpy2 (#134)
Browse files Browse the repository at this point in the history
* try numpy 2.0 builds

* fix index print bug
  • Loading branch information
samuelstjean committed Jul 25, 2024
1 parent 0b00d57 commit 47d094f
Show file tree
Hide file tree
Showing 12 changed files with 63 additions and 43 deletions.
30 changes: 20 additions & 10 deletions .github/workflows/cibuildwheel.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest, macos-14]
os: [ubuntu-latest, windows-latest, macos-12, macos-14]

steps:
- uses: actions/checkout@v4
Expand All @@ -24,23 +24,32 @@ jobs:
python-version: '3.10'

- name: Build wheels
uses: pypa/cibuildwheel@v2.16.5
uses: pypa/cibuildwheel@v2.19.2
env:
CIBW_BUILD_FRONTEND: "build"
CIBW_ARCHS: "auto64"
CIBW_SKIP: "pp* cp36-* cp37-* *musllinux* cp38-macosx_arm64"
CIBW_SKIP: "pp* *musllinux*"
CIBW_MANYLINUX_X86_64_IMAGE: manylinux_2_28
CIBW_TEST_REQUIRES: pytest
CIBW_TEST_COMMAND: pytest --pyargs nlsam --verbose
CIBW_TEST_COMMAND_LINUX: >
CIBW_TEST_COMMAND_WINDOWS: pytest --pyargs nlsam --verbose
CIBW_TEST_COMMAND: >
pytest --pyargs nlsam --verbose &&
cd {package}/example &&
chmod +x {package}/nlsam/tests/test_scripts1.sh &&
chmod +x {package}/nlsam/tests/test_scripts1.sh &&
bash {package}/nlsam/tests/test_scripts1.sh &&
chmod +x {package}/nlsam/tests/test_scripts2.sh &&
chmod +x {package}/nlsam/tests/test_scripts2.sh &&
bash {package}/nlsam/tests/test_scripts2.sh
- name: Test against oldest supported numpy version
shell: bash
run: |
python -m pip install ./wheelhouse/*cp310*.whl
python -m pip install numpy==1.21.3 scipy==1.8 pytest
pytest --pyargs nlsam --verbose
- uses: actions/upload-artifact@v4
with:
name: nlsam_${{ runner.os }}
name: cibw-nlsam_${{ matrix.os }}
path: ./wheelhouse/*.whl

build_sdist:
Expand All @@ -61,14 +70,15 @@ jobs:
- uses: actions/upload-artifact@v4
with:
name: cibw-sdist
path: dist/*.tar.gz

build_pyinstaller:
name: Build pyinstaller on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-20.04, windows-2019, macos-11]
os: [ubuntu-20.04, windows-2019, macos-12, macos-14]
steps:
- uses: actions/checkout@v4

Expand All @@ -92,7 +102,7 @@ jobs:
- uses: actions/upload-artifact@v4
with:
name: nlsam_${{ env.version }}_${{ runner.os }}
name: nlsam_${{ env.version }}_${{ runner.os }}_${{ runner.arch }}
path: |
./dist/nlsam_denoising*
CHANGELOG.md
Expand Down
20 changes: 9 additions & 11 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ on:
jobs:
publish_artifacts:
runs-on: ubuntu-latest
environment: release
permissions:
# IMPORTANT: this permission is mandatory for trusted publishing
id-token: write
# upload to PyPI on every tag starting with 'v'
# if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/v')
# alternatively, to publish when a GitHub Release is created, use the following rule:
Expand All @@ -17,23 +21,17 @@ jobs:
- name: Download builds
uses: actions/download-artifact@v4
with:
name: artifact
path: dist
pattern: cibw-*
merge-multiple: true

- name: upload to pypi
uses: pypa/gh-action-pypi-publish@release/v1
with:
verbose: true
print-hash: true
user: ${{ secrets.PYPI_USERNAME }}
password: ${{ secrets.PYPI_PASSWORD }}
# password: ${{ secrets.testpypi_password }}
# repository_url: https://test.pypi.org/legacy/

- name: publish to github release
uses: softprops/action-gh-release@v1
# permissions:
# contents: write
uses: softprops/action-gh-release@v2
permissions:
contents: write
if: startsWith(github.ref, 'refs/tags/')
with:
files: dist/*
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,18 @@ var/
.ipynb_checkpoints
.DS_Store
pip-wheel-metadata/*
*.whl

# 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
log

# Unit test / coverage reports
htmlcov/
Expand All @@ -43,6 +46,7 @@ htmlcov/
.cache
nosetests.xml
coverage.xml
*.html

# Translations
*.mo
Expand All @@ -61,3 +65,5 @@ target/

nlsam.zip
*.code-workspace
*.nii.gz
*.nii
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## [0.7.2] - 2023-07-03

- Support for numpy 2.0 and python 3.9 and up
- Fixes for Cython 3 and newer Scipy

## [0.7.1] - 2023-07-03

- Some speed improvements internally
Expand Down
4 changes: 2 additions & 2 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,9 @@
# built documents.
#
# The short X.Y version.
version = '0.7.1'
version = '0.7.2'
# The full version, including alpha/beta/rc tags.
release = '0.7.1'
release = '0.7.2'

# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
Expand Down
8 changes: 4 additions & 4 deletions nlsam/bias_correction.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,14 +85,14 @@ def root_finder_sigma(data, sigma, N, mask=None, verbose=False, n_cores=-1):
output, ndarray
Corrected sigma value, where sigma_gaussian = sigma / sqrt(xi)
"""
data = np.array(data)
sigma = np.array(sigma)
N = np.array(N)
data = np.asarray(data)
sigma = np.asarray(sigma)
N = np.asarray(N)

if mask is None:
mask = np.ones(data.shape[:-1], dtype=bool)
else:
mask = np.array(mask, dtype=bool)
mask = np.asarray(mask, dtype=bool)

# Force 3D/4D broadcasting if needed
if sigma.ndim == (data.ndim - 1):
Expand Down
6 changes: 3 additions & 3 deletions nlsam/denoiser.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,8 @@ def nlsam_denoise(data, sigma, bvals, bvecs, block_size,
if not ((dtype == np.float32) or (dtype == np.float64)):
raise ValueError(f'dtype should be either np.float32 or np.float64, but is {dtype}')

b0_loc = np.where(bvals <= b0_threshold)[0]
dwis = np.where(bvals > b0_threshold)[0]
b0_loc = np.flatnonzero(bvals <= b0_threshold)
dwis = np.flatnonzero(bvals > b0_threshold)
num_b0s = len(b0_loc)
variance = sigma**2
angular_size = block_size[-1]
Expand Down Expand Up @@ -166,7 +166,7 @@ def nlsam_denoise(data, sigma, bvals, bvecs, block_size,
to_denoise[..., 1:] = data[..., idx]
divider[list(b0_loc + idx)] += 1

logger.info(f'Now denoising volumes {b0_loc + idx} / block {i} out of {len(indexes)}.')
logger.info(f'Now denoising volumes {np.hstack((b0_loc, idx))} / block {i} out of {len(indexes)}.')

data_denoised[..., b0_loc + idx] += local_denoise(to_denoise,
b0_block_size,
Expand Down
5 changes: 3 additions & 2 deletions nlsam/stabilizer.pyx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# cython: wraparound=False, cdivision=True, boundscheck=False, language_level=3, embedsignature=True, infer_types=True
# distutils: define_macros=NPY_NO_DEPRECATED_API=NPY_1_7_API_VERSION

import numpy as np
cimport numpy as np
Expand Down Expand Up @@ -293,7 +294,7 @@ cdef inline double xi(double eta, double sigma, double N) noexcept nogil:
# It starts to accumulate error around SNR ~ 1e4 though,
# so we clip it to 1 (if needed) to stay on the safe side.

if fabs(out) > 1:
if fabs(out) > 1.0:
out = 1.0

return out
Expand Down Expand Up @@ -349,7 +350,7 @@ def root_finder_loop(const floating[:] data, const floating1[:] sigma, const flo
for idx in range(imax):
theta = data[idx] / sigma[idx]
gaussian_SNR = root_finder(theta, N[idx])
corrected_sigma[idx] = sigma[idx] / sqrt(xi(gaussian_SNR, 1, N[idx]))
corrected_sigma[idx] = sigma[idx] / sqrt(xi(gaussian_SNR, 1.0, N[idx]))

return corrected_sigma

Expand Down
6 changes: 3 additions & 3 deletions nlsam/tests/test_bias_correction.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ def test_root_finder_sigma():
assert_allclose(output, sigma, atol=1e-4)

# magnitude SNR of 0.5 -> sigma = 50, eta = 25 for N = 1
a = [np.sqrt((25 + np.random.randn(10000) * 50)**2 + (np.random.randn(10000) * 50)**2) for ii in range(250)]
a = [np.sqrt((25 + np.random.randn(10000) * 50)**2 + (np.random.randn(10000) * 50)**2) for ii in range(1250)]
eta = np.mean(a, axis=1, keepdims=True, dtype=np.float64)
sigma = np.std(a, axis=1, keepdims=True, dtype=np.float64)
N = np.ones(sigma.shape, dtype=np.float32)
output = root_finder_sigma(eta, sigma, N)

# everything less than 10% error of real value?
assert_allclose(output, 50, atol=5)
# everything less than 12% error of real value?
assert_allclose(output, 50, atol=6)


# Taken from original example
Expand Down
4 changes: 2 additions & 2 deletions nlsam/tests/test_scripts1.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ python -c 'import nibabel as nib; import numpy as np; d = nib.load("mask.nii.gz"
python -c 'import nibabel as nib; import numpy as np; d = nib.load("mask.nii.gz").get_fdata(); nib.save(nib.Nifti1Image(np.random.rayleigh(10, d[40:50, 80:85].shape),np.eye(4)), "noise.nii.gz")'

# Test on example dataset
nlsam_denoising dwi_crop.nii.gz dwi_nlsam.nii.gz bvals bvecs -f -N 1 --noise_est local_std --sh_order 0 --log log --cores 1 -m mask_crop.nii.gz
nlsam_denoising dwi_crop.nii.gz dwi_nlsam.nii.gz bvals bvecs -f -N 1 --noise_est local_std --sh_order 0 --cores 1 -m mask_crop.nii.gz -v
check_return_code $?

nlsam_denoising dwi_crop.nii.gz dwi_nlsam.nii.gz bvals bvecs -m mask_crop.nii.gz -f -N 1 --noise_est local_std --sh_order 6 --iterations 5 --verbose --save_sigma sigma.nii.gz
nlsam_denoising dwi_crop.nii.gz dwi_nlsam.nii.gz bvals bvecs -m mask_crop.nii.gz -f -N 1 --noise_est local_std --sh_order 6 --iterations 5 --verbose --save_sigma sigma.nii.gz --log log.txt
check_return_code $?

nlsam_denoising dwi_crop.nii.gz dwi_nlsam.nii.gz bvals bvecs -m mask_crop.nii.gz -f --noise_est auto --sh_order 6 --iterations 5 --verbose --save_sigma sigma.nii.gz --save_N N.nii.gz
Expand Down
1 change: 1 addition & 0 deletions nlsam/utils.pyx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# cython: wraparound=False, cdivision=True, boundscheck=False, language_level=3, embedsignature=True, infer_types=True
# distutils: define_macros=NPY_NO_DEPRECATED_API=NPY_1_7_API_VERSION

import numpy as np

Expand Down
11 changes: 5 additions & 6 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,25 +1,24 @@
[build-system]
requires = ["Cython>=0.29.33",
requires = ["Cython>=3.0",
"scipy>=1.5",
"oldest-supported-numpy",
"numpy>=2.0",
"setuptools",
"wheel"]
build-backend = "setuptools.build_meta"


[project]
name = "nlsam"
version = '0.7.1'
version = '0.7.2'
authors = [{name = "Samuel St-Jean"}]
description='Implementation of "Non Local Spatial and Angular Matching : Enabling higher spatial resolution diffusion MRI datasets through adaptive denoising"'
readme = "README.md"
requires-python = ">=3.7"
requires-python = ">=3.9"
license = {text = "GPLv3"}

dependencies = [
'numpy>=1.19',
'numpy>=1.21.3',
'scipy>=1.5',
'cython>=0.29.33',
'nibabel>=2.0',
'joblib>=0.14.1',
'autodmri>=0.2.1',
Expand Down

0 comments on commit 47d094f

Please sign in to comment.