diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b48806b --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +*~ +*.DS_Store +*.egg-info +__pycache__ +docs/build +.coverage +htmlcov +.docker +.idea/ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..52768ac --- /dev/null +++ b/Dockerfile @@ -0,0 +1,38 @@ +# Docker file for mriseg ChRIS plugin app +# +# Build with +# +# docker build -t . +# +# For example if building a local version, you could do: +# +# docker build -t local/pl-mriseg . +# +# In the case of a proxy (located at 192.168.13.14:3128), do: +# +# docker build --build-arg http_proxy=http://192.168.13.14:3128 --build-arg UID=$UID -t local/pl-mriseg . +# +# To run an interactive shell inside this container, do: +# +# docker run -ti --entrypoint /bin/bash local/pl-mriseg +# +# To pass an env var HOST_IP to container, do: +# +# docker run -ti -e HOST_IP=$(ip route | grep -v docker | awk '{if(NF==11) print $9}') --entrypoint /bin/bash local/pl-mriseg +# + + + +FROM fnndsc/ubuntu-python3:latest +MAINTAINER fnndsc "dev@babymri.org" + +ENV APPROOT="/usr/src/mriseg" +COPY ["mriseg", "${APPROOT}"] +COPY ["requirements.txt", "${APPROOT}"] + +WORKDIR $APPROOT + +RUN pip install --upgrade pip +RUN pip install -r requirements.txt + +CMD ["mriseg.py", "--help"] \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..78c9a06 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 FNNDSC / BCH + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..eb6934a --- /dev/null +++ b/README.rst @@ -0,0 +1,113 @@ +pl-mriseg +================================ + +.. image:: https://badge.fury.io/py/mriseg.svg + :target: https://badge.fury.io/py/mriseg + +.. image:: https://travis-ci.org/FNNDSC/mriseg.svg?branch=master + :target: https://travis-ci.org/FNNDSC/mriseg + +.. image:: https://img.shields.io/badge/python-3.5%2B-blue.svg + :target: https://badge.fury.io/py/pl-mriseg + +.. contents:: Table of Contents + + +Abstract +-------- + +An app to perform 3D segmentation on Brain MRI data + + +Synopsis +-------- + +.. code:: + + python mriseg.py \ + [-v ] [--verbosity ] \ + [--version] \ + [--man] \ + [--meta] \ + + + +Description +----------- + +``mriseg.py`` is a ChRIS-based application that... + +Agruments +--------- + +.. code:: + + [-v ] [--verbosity ] + Verbosity level for app. Not used currently. + + [--version] + If specified, print version number. + + [--man] + If specified, print (this) man page. + + [--meta] + If specified, print plugin meta data. + + +Run +---- + +This ``plugin`` can be run in two modes: natively as a python package or as a containerized docker image. + +Using PyPI +~~~~~~~~~~ + +To run from PyPI, simply do a + +.. code:: bash + + pip install mriseg + +and run with + +.. code:: bash + + mriseg.py --man /tmp /tmp + +to get inline help. The app should also understand being called with only two positional arguments + +.. code:: bash + + mriseg.py /some/input/directory /destination/directory + + +Using ``docker run`` +~~~~~~~~~~~~~~~~~~~~ + +To run using ``docker``, be sure to assign an "input" directory to ``/incoming`` and an output directory to ``/outgoing``. *Make sure that the* ``$(pwd)/out`` *directory is world writable!* + +Now, prefix all calls with + +.. code:: bash + + docker run --rm -v $(pwd)/out:/outgoing \ + fnndsc/pl-mriseg mriseg.py \ + +Thus, getting inline help is: + +.. code:: bash + + mkdir in out && chmod 777 out + docker run --rm -v $(pwd)/in:/incoming -v $(pwd)/out:/outgoing \ + fnndsc/pl-mriseg mriseg.py \ + --man \ + /incoming /outgoing + +Examples +-------- + + + + + diff --git a/mriseg/__init__.py b/mriseg/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/mriseg/mriseg.py b/mriseg/mriseg.py new file mode 100755 index 0000000..a43c34a --- /dev/null +++ b/mriseg/mriseg.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python +# +# mriseg ds ChRIS plugin app +# +# (c) 2016-2019 Fetal-Neonatal Neuroimaging & Developmental Science Center +# Boston Children's Hospital +# +# http://childrenshospital.org/FNNDSC/ +# dev@babyMRI.org +# + + +import os +import sys +sys.path.append(os.path.dirname(__file__)) + +# import the Chris app superclass +from chrisapp.base import ChrisApp + + +Gstr_title = """ + +Generate a title from +http://patorjk.com/software/taag/#p=display&f=Doom&t=mriseg + +""" + +Gstr_synopsis = """ + +(Edit this in-line help for app specifics. At a minimum, the +flags below are supported -- in the case of DS apps, both +positional arguments and ; for FS apps +only -- and similarly for directories +where necessary.) + + NAME + + mriseg.py + + SYNOPSIS + + python mriseg.py \\ + [-h] [--help] \\ + [--json] \\ + [--man] \\ + [--meta] \\ + [--savejson ] \\ + [-v ] [--verbosity ] \\ + [--version] \\ + \\ + + + BRIEF EXAMPLE + + * Bare bones execution + + mkdir in out && chmod 777 out + python mriseg.py \\ + in out + + DESCRIPTION + + `mriseg.py` ... + + ARGS + + [-h] [--help] + If specified, show help message and exit. + + [--json] + If specified, show json representation of app and exit. + + [--man] + If specified, print (this) man page and exit. + + [--meta] + If specified, print plugin meta data and exit. + + [--savejson ] + If specified, save json representation file to DIR and exit. + + [-v ] [--verbosity ] + Verbosity level for app. Not used currently. + + [--version] + If specified, print version number and exit. + +""" + + +class Mriseg(ChrisApp): + """ + An app to perform 3D segmentation on Brain MRI data. + """ + AUTHORS = 'Sandip Samal (sandip.samal@childrens.harvard.edu)' + SELFPATH = os.path.dirname(os.path.abspath(__file__)) + SELFEXEC = os.path.basename(__file__) + EXECSHELL = 'python3' + TITLE = '3D segmentation app' + CATEGORY = '' + TYPE = 'ds' + DESCRIPTION = 'An app to perform 3D segmentation on Brain MRI data' + DOCUMENTATION = 'http://wiki' + VERSION = '0.1' + ICON = '' # url of an icon image + LICENSE = 'Opensource (MIT)' + MAX_NUMBER_OF_WORKERS = 1 # Override with integer value + MIN_NUMBER_OF_WORKERS = 1 # Override with integer value + MAX_CPU_LIMIT = '' # Override with millicore value as string, e.g. '2000m' + MIN_CPU_LIMIT = '' # Override with millicore value as string, e.g. '2000m' + MAX_MEMORY_LIMIT = '' # Override with string, e.g. '1Gi', '2000Mi' + MIN_MEMORY_LIMIT = '' # Override with string, e.g. '1Gi', '2000Mi' + MIN_GPU_LIMIT = 0 # Override with the minimum number of GPUs, as an integer, for your plugin + MAX_GPU_LIMIT = 0 # Override with the maximum number of GPUs, as an integer, for your plugin + + # Use this dictionary structure to provide key-value output descriptive information + # that may be useful for the next downstream plugin. For example: + # + # { + # "finalOutputFile": "final/file.out", + # "viewer": "genericTextViewer", + # } + # + # The above dictionary is saved when plugin is called with a ``--saveoutputmeta`` + # flag. Note also that all file paths are relative to the system specified + # output directory. + OUTPUT_META_DICT = {} + + def define_parameters(self): + """ + Define the CLI arguments accepted by this plugin app. + Use self.add_argument to specify a new app argument. + """ + + def run(self, options): + """ + Define the code to be run by this plugin app. + """ + print(Gstr_title) + print('Version: %s' % self.get_version()) + + def show_man_page(self): + """ + Print the app's man page. + """ + print(Gstr_synopsis) + + +# ENTRYPOINT +if __name__ == "__main__": + chris_app = Mriseg() + chris_app.launch() diff --git a/mriseg/tests/__init__.py b/mriseg/tests/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/mriseg/tests/test_mriseg.py b/mriseg/tests/test_mriseg.py new file mode 100644 index 0000000..5bd09ad --- /dev/null +++ b/mriseg/tests/test_mriseg.py @@ -0,0 +1,33 @@ + +from unittest import TestCase +from unittest import mock +from mriseg.mriseg import Mriseg + + +class MrisegTests(TestCase): + """ + Test Mriseg. + """ + def setUp(self): + self.app = Mriseg() + + def test_run(self): + """ + Test the run code. + """ + args = [] + if self.app.TYPE == 'ds': + args.append('inputdir') # you may want to change this inputdir mock + args.append('outputdir') # you may want to change this outputdir mock + + # you may want to add more of your custom defined optional arguments to test + # your app with + # eg. + # args.append('--custom-int') + # args.append(10) + + options = self.app.parse_args(args) + self.app.run(options) + + # write your own assertions + self.assertEqual(options.outputdir, 'outputdir') diff --git a/release.sh b/release.sh new file mode 100755 index 0000000..25259e7 --- /dev/null +++ b/release.sh @@ -0,0 +1,61 @@ +#!/bin/bash + +G_SYNOPSIS=" + + NAME + + release.sh + + SYNOPSIS + + release.sh [--pypi] + + ARGS + + + A version string. For best practices make this version the same as + the VERSION class variable defined in your ChrisApp-derived Python class. + + --pypi + Optional flag to also upload the new version to PyPI. + + DESCRIPTION + + release.sh is a simple helper script to tag and upload a new version of the plugin + app to Github. If --pypi is passed then the new version is also uploaded to PyPI + (Python Package Index). + + +" + +if [[ "$#" -eq 0 ]] || [[ "$#" -gt 2 ]]; then + echo "$G_SYNOPSIS" + exit 1 +fi +VER=$1 +if [[ "$#" -eq 2 ]]; then + if [[ "$1" != '--pypi' ]] && [[ "$2" != '--pypi' ]]; then + echo "$G_SYNOPSIS" + exit 1 + fi + if [[ "$1" == '--pypi' ]]; then + VER=$2 + fi +fi +if [[ $VER =~ ^[0-9.]+$ ]]; then + echo Pushing $VER to Github ... + git add -A + git commit -m "v${VER}" + git push origin master + git tag $VER + git push origin --tags +else + echo "Invalid version number format $VER." + exit 1 +fi +if [[ "$#" -eq 2 ]]; then + echo Pushing $VER to PyPI ... + rstcheck README.rst + python3 setup.py sdist + twine upload dist/mriseg-${VER}.tar.gz +fi diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..f011de5 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +chrisapp +pudb diff --git a/setup.py b/setup.py new file mode 100755 index 0000000..7e39be8 --- /dev/null +++ b/setup.py @@ -0,0 +1,37 @@ + +import sys +import os + + +# Make sure we are running python3.5+ +if 10 * sys.version_info[0] + sys.version_info[1] < 35: + sys.exit("Sorry, only Python 3.5+ is supported.") + + +from setuptools import setup + + +def readme(): + print("Current dir = %s" % os.getcwd()) + print(os.listdir()) + with open('README.rst') as f: + return f.read() + +setup( + name = 'mriseg', + # for best practices make this version the same as the VERSION class variable + # defined in your ChrisApp-derived Python class + version = '0.1', + description = 'An app to perform 3D segmentation on Brain MRI data', + long_description = readme(), + author = 'Sandip Samal', + author_email = 'sandip.samal@childrens.harvard.edu', + url = 'http://wiki', + packages = ['mriseg'], + install_requires = ['chrisapp', 'pudb'], + test_suite = 'nose.collector', + tests_require = ['nose'], + scripts = ['mriseg/mriseg.py'], + license = 'MIT', + zip_safe = False + )