Skip to content

Commit

Permalink
bump version, merge pull request #5 from AMYPAD/cuda
Browse files Browse the repository at this point in the history
  • Loading branch information
casperdcl authored Nov 19, 2020
2 parents 3286a32 + 2b44278 commit af71fdf
Show file tree
Hide file tree
Showing 9 changed files with 218 additions and 19 deletions.
8 changes: 2 additions & 6 deletions .github/workflows/comment-bot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ on:
types: [created]
pull_request_review_comment:
types: [created]

jobs:
tag: # /tag <tagname> <commit>
if: startsWith(github.event.comment.body, '/tag ')
Expand Down Expand Up @@ -33,13 +32,10 @@ jobs:
comment_id: context.payload.comment.id, content: "eyes"})
- name: Tag Commit
run: |
git clone https://${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY} repo
git -C repo tag $(echo "$BODY" | awk '{print $2" "$3}')
git -C repo push --tags
rm -rf repo
git tag $(echo "$BODY" | awk '{print $2" "$3}')
git push --tags
env:
BODY: ${{ github.event.comment.body }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: React Success
uses: actions/github-script@v2
with:
Expand Down
13 changes: 10 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,21 @@ jobs:
python-version: ${{ matrix.python }}
- run: pip install -U .[dev]
- run: python -m tests --cov-report=term-missing --cov-report=xml
- run: codecov
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
matlab:
runs-on: [self-hosted, matlab]
name: Test matlab
name: Test matlab & cuda
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- run: pip install -U .[dev]
- run: pip install -U .[dev,cuda]
- run: python -m tests --cov-report=term-missing --cov-report=xml
- run: codecov
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
deploy:
if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags')
needs: [check, matlab, test]
Expand All @@ -57,5 +63,6 @@ jobs:
- uses: actions/checkout@v2
- uses: casperdcl/deploy-pypi@v1
with:
password: ${{ secrets.pypi_token }}
password: ${{ secrets.PYPI_TOKEN }}
build: true
gpg_key: ${{ secrets.GPG_KEY }}
20 changes: 17 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ miutil

Medical imaging utilities.

|Versions| |Py-Versions| |Tests| |LICENCE|
|Versions| |Py-Versions| |Tests| |Coverage| |LICENCE|

Basic functionality needed for `AMYPAD <https://github.com/AMYPAD/AMYPAD>`_
and `NiftyPET <https://github.com/NiftyPET/NiftyPET>`_.
Expand All @@ -13,17 +13,31 @@ Install
-------

Intended for inclusion in requirements for other packages.
The package name is ``miutil``. Options include:
The package name is ``miutil``. Extra install options include:

- nii

- provides `miutil.imio.nii <https://github.com/AMYPAD/miutil/blob/master/miutil/imio/nii.py>`_

- plot

To install options and their dependencies,
- provides `miutil.plot <https://github.com/AMYPAD/miutil/blob/master/miutil/plot.py>`_

- includes a useful 3D multi-volume ``imscroll`` function which depends only on ``matplotlib``

- cuda

- provides `miutil.cuinfo <https://github.com/AMYPAD/miutil/blob/master/miutil/cuinfo.py>`_


To install extras and their dependencies,
use the package name ``miutil[option1,option2]``.


.. |Tests| image:: https://img.shields.io/github/workflow/status/AMYPAD/miutil/Test
:target: https://github.com/AMYPAD/miutil/actions
.. |Coverage| image:: https://codecov.io/gh/AMYPAD/miutil/branch/master/graph/badge.svg
:target: https://codecov.io/gh/AMYPAD/miutil
.. |Versions| image:: https://img.shields.io/pypi/v/miutil.svg
:target: https://github.com/AMYPAD/miutil/releases
.. |Py-Versions| image:: https://img.shields.io/pypi/pyversions/miutil.svg?logo=python&logoColor=white
Expand Down
84 changes: 84 additions & 0 deletions miutil/cuinfo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
"""CUDA helpers
Usage:
miutil.cuinfo [options]
Options:
-n, --num-devices : print number of devices (ignores `-d`)
-f, --nvcc-flags : print out flags for use nvcc compilation
-d ID, --dev-id ID : select device ID [default: None:int] for all
"""
from argopt import argopt
import pynvml

__all__ = ["num_devices", "compute_capability", "memory", "name", "nvcc_flags"]


def nvmlDeviceGetCudaComputeCapability(handle):
major = pynvml.c_int()
minor = pynvml.c_int()
fn = pynvml.get_func_pointer("nvmlDeviceGetCudaComputeCapability")
ret = fn(handle, pynvml.byref(major), pynvml.byref(minor))
pynvml.check_return(ret)
return [major.value, minor.value]


def num_devices():
"""returns total number of devices"""
pynvml.nvmlInit()
return pynvml.nvmlDeviceGetCount()


def get_handle(dev_id=-1):
"""allows negative indexing"""
pynvml.nvmlInit()
dev_id = num_devices() + dev_id if dev_id < 0 else dev_id
try:
return pynvml.nvmlDeviceGetHandleByIndex(dev_id)
except pynvml.NVMLError:
raise IndexError("invalid dev_id")


def compute_capability(dev_id=-1):
"""returns compute capability (major, minor)"""
return tuple(nvmlDeviceGetCudaComputeCapability(get_handle(dev_id)))


def memory(dev_id=-1):
"""returns memory (total, free, used)"""
mem = pynvml.nvmlDeviceGetMemoryInfo(get_handle(dev_id))
return (mem.total, mem.free, mem.used)


def name(dev_id=-1):
"""returns device name"""
return pynvml.nvmlDeviceGetName(get_handle(dev_id)).decode("U8")


def nvcc_flags(dev_id=-1):
return "-gencode=arch=compute_{0:d}{1:d},code=compute_{0:d}{1:d}".format(
*compute_capability(dev_id)
)


def main(*args, **kwargs):
args = argopt(__doc__).parse_args(*args, **kwargs)
noargs = True
devices = range(num_devices()) if args.dev_id is None else [args.dev_id]

if args.num_devices:
print(num_devices())
noargs = False
if args.nvcc_flags:
print(" ".join(sorted(set(map(nvcc_flags, devices)))[::-1]))
noargs = False
if noargs:
for dev_id in devices:
print(
"Device {:2d}:{}:compute capability:{:d}.{:d}".format(
dev_id, name(dev_id), *compute_capability(dev_id)
)
)


if __name__ == "__main__": # pragma: no cover
main()
10 changes: 5 additions & 5 deletions miutil/imio/nii.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import numpy as np

from . import RE_NII_GZ
from ..fdio import create_dir
from ..fdio import create_dir, hasext

RE_GZ = re.compile(r"^(.+)(\.gz)$", flags=re.I)
log = logging.getLogger(__name__)
Expand All @@ -25,11 +25,11 @@ def file_parts(fname, regex=RE_NII_GZ):

def nii_ugzip(imfile, outpath=""):
"""Uncompress *.gz file"""
assert imfile[-3:].lower() == ".gz"
assert hasext(imfile, "gz")
fout, ext = file_parts(imfile, RE_GZ)
with gzip.open(imfile, "rb") as f:
s = f.read()
# write the uncompressed data
fout = imfile[:-3]
if outpath:
fout = os.path.join(outpath, os.path.basename(fout))
with open(fout, "wb") as f:
Expand Down Expand Up @@ -141,11 +141,11 @@ def array2nii(im, A, fnii, descrip="", trnsp=None, flip=None, storage_as=None):
'descrip': the description given to the file
'trsnp': transpose/permute the dimensions.
In NIfTI it has to be in this order: [x,y,z,t,...])
'flip': flip tupple for flipping the direction of x,y,z axes.
'flip': flip tuple for flipping the direction of x,y,z axes.
(1: no flip, -1: flip)
'storage_as': uses the flip and displacement as given by the following
NifTI dictionary, obtained using
nimpa.getnii(filepath, output='all').
`getnii(filepath, output='all')`.
"""
trnsp = trnsp or ()
flip = flip or ()
Expand Down
6 changes: 6 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ provides = miutil
classifiers =
Development Status :: 4 - Beta
Intended Audience :: Developers
Environment :: GPU
Environment :: GPU :: NVIDIA CUDA
Intended Audience :: Education
Intended Audience :: Healthcare Industry
Intended Audience :: Science/Research
Expand Down Expand Up @@ -54,13 +56,17 @@ dev =
pytest-cov
pytest-timeout
pytest-xdist
codecov
nii =
nibabel
numpy
plot =
matplotlib
numpy
scipy
cuda =
argopt
pynvml
[options.package_data]
* = *.md, *.rst, *.m

Expand Down
48 changes: 48 additions & 0 deletions tests/test_cuinfo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from pytest import importorskip, raises

cuinfo = importorskip("miutil.cuinfo")


def test_num_devices():
devices = cuinfo.num_devices()
assert isinstance(devices, int)


def test_compute_capability():
cc = cuinfo.compute_capability()
if cc:
assert len(cc) == 2, cc
assert all(isinstance(i, int) for i in cc), cc


def test_memory():
mem = cuinfo.memory()
if mem:
assert len(mem) == 3, mem
assert all(isinstance(i, int) for i in mem), mem


def test_cuinfo_cli(capsys):
cuinfo.main(["--num-devices"])
out, _ = capsys.readouterr()
devices = int(out)
assert devices >= 0

# individual dev_id
for dev_id in range(devices):
cuinfo.main(["--dev-id", str(dev_id)])
out, _ = capsys.readouterr()
assert len(out.split("Device ")) == devices + 1

# all dev_ids
cuinfo.main()
out, _ = capsys.readouterr()
assert len(out.split("Device ")) == devices + 1

# dev_id one too much
with raises(IndexError):
cuinfo.main(["--dev-id", str(devices)])

cuinfo.main(["--nvcc-flags"])
out, _ = capsys.readouterr()
assert not devices or out.startswith("-gencode=")
34 changes: 34 additions & 0 deletions tests/test_fdio.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from os import path

from miutil import fdio


def test_create_dir(tmp_path):
tmpdir = str(tmp_path / "create_dir")
assert not path.exists(tmpdir)
fdio.create_dir(tmpdir)
assert path.exists(tmpdir)


def test_hasext():
for fname, ext in [
("foo.bar", ".bar"),
("foo.bar", "bar"),
("foo.bar.baz", "baz"),
("foo/bar.baz", "baz"),
]:
assert fdio.hasext(fname, ext)

for fname, ext in [
("foo.bar", "baz"),
("foo.bar.baz", "bar.baz"),
("foo", "foo"),
]:
assert not fdio.hasext(fname, ext)


def test_tmpdir():
with fdio.tmpdir() as tmpdir:
assert path.exists(tmpdir)
res = tmpdir
assert not path.exists(res)
14 changes: 12 additions & 2 deletions tests/test_imio.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

from miutil.imio import imread

np = importorskip("numpy")

def test_imread(tmp_path):
np = importorskip("numpy")

def test_imread(tmp_path):
x = np.random.randint(10, size=(9, 9))
fname = tmp_path / "test_imread.npy"
np.save(fname, x)
Expand All @@ -17,3 +17,13 @@ def test_imread(tmp_path):

np.savez(fname, x, x)
assert (imread(str(fname))["arr_0"] == x).all()


def test_nii(tmp_path):
nii = importorskip("miutil.imio.nii")

x = np.arange(2 * 2 * 3).reshape(2, 2, 3)
fname = str(tmp_path / "test_nii.nii")
nii.array2nii(x, np.eye(4), fname, flip=(1, 1, 1))
nii.nii_gzip(fname)
assert (imread(fname + ".gz") == x).all()

0 comments on commit af71fdf

Please sign in to comment.