Skip to content

Commit

Permalink
bump version, merge pull request #18 from AMYPAD/web
Browse files Browse the repository at this point in the history
  • Loading branch information
casperdcl authored Dec 6, 2021
2 parents b5b9629 + 0f37a62 commit e7b727f
Show file tree
Hide file tree
Showing 5 changed files with 231 additions and 39 deletions.
4 changes: 3 additions & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ Requires Python 3.6 or greater. Choose one of the following:

.. code:: sh
pip install amypet # command line interface (CLI) version
pip install amypet[web] # CLI and web user interface (Web UI) version (recommended)
pip install amypet # CLI version
pip install amypet[gui] # CLI and graphical user interface (GUI) version
For certain functionality (image trimming & resampling, DICOM to NIfTI converters) it may be required to also `pip install nimpa`.
Expand All @@ -24,6 +25,7 @@ Usage

.. code:: sh
amypet.web # Web UI version
amypet --help # CLI version
amypet.gui # GUI version
Expand Down
167 changes: 167 additions & 0 deletions amypet/_backend_web.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import logging
import shlex
import sys
import tkinter as tk
from argparse import (
SUPPRESS,
_HelpAction,
_StoreAction,
_StoreTrueAction,
_SubParsersAction,
_VersionAction,
)
from pathlib import Path

import streamlit as st
from packaging.version import Version
from streamlit.version import _get_installed_streamlit_version

from amypet.gui import BaseParser, __licence__, __version__, get_main_parser, patch_argument_kwargs

NONE = ''
PARSER = '==PARSER=='
log = logging.getLogger(__name__)
THIS = Path(__file__).parent
CONFIG = {
'page_title': "AmyPET", 'page_icon': str(THIS / "program_icon.png"), 'layout': 'wide',
'initial_sidebar_state': 'expanded'}
if _get_installed_streamlit_version() >= Version("0.88.1"):
CONFIG['menu_items'] = {
"Get help": "https://github.com/AMYPAD/AmyPET/issues", "Report a Bug": None, "About": f"""
AmyPET Pipeline
***version**: {__version__}
*GUI to run AmyPET tools* ([Source Code](https://github.com/AMYPAD/amypet)).
An https://amypad.eu Project.
{__licence__}"""}

root = tk.Tk()
root.withdraw()
root.wm_attributes('-topmost', 1)


class MyParser(BaseParser):
def add_argument(self, *args, **kwargs):
kwargs = patch_argument_kwargs(kwargs, gooey=True)
widget = kwargs.pop('widget', None)
log.debug("%r, %r", args, kwargs)
res = super(MyParser, self).add_argument(*args, **kwargs)
if widget is not None:
res.widget = widget
return res


def main():
logging.basicConfig(level=logging.DEBUG)
st.set_page_config(**CONFIG)
parser = get_main_parser(gui_mode=False, argparser=MyParser)
opts = {}

def recurse(parser, key_prefix=""):
opts[PARSER] = parser
st.write(f"{'#' * (key_prefix.count('_') + 1)} {parser.prog}")

for opt in parser._actions:
if isinstance(opt, (_HelpAction, _VersionAction)) or opt.dest in {'dry_run'}:
continue
elif isinstance(opt, _StoreTrueAction):
val = st.checkbox(opt.dest, value=opt.default, help=opt.help,
key=f"{key_prefix}{opt.dest}")
if val != opt.default:
opts[opt.dest] = val
elif isinstance(opt, _StoreAction):
dflt = NONE if opt.default is None else opt.default
kwargs = {'help': opt.help, 'key': f"{key_prefix}{opt.dest}"}
if hasattr(opt, 'widget'):
if opt.widget == "MultiFileChooser":
val = [
i.name for i in st.file_uploader(opt.dest, accept_multiple_files=True,
**kwargs)]
elif opt.widget == "FileChooser":
val = getattr(
st.file_uploader(opt.dest, accept_multiple_files=False, **kwargs),
'name', NONE)
elif opt.widget == "DirChooser":
# https://github.com/streamlit/streamlit/issues/1019
left, right = st.columns(2)
with left:
st.caption(opt.dest)
with right:
clicked = st.button("Browse directories", **kwargs)
key = f'{key_prefix}{opt.dest}_val'
if clicked:
st.session_state[key] = tk.filedialog.askdirectory(
master=root, initialdir=dflt) or dflt
val = st.session_state.get(key, dflt)
with left:
st.write(f"`{val or '(blank)'}`")
elif opt.widget == "IntegerField":
dflt = opt.default or 0
val = st.number_input(opt.dest,
min_value=int(parser.options[opt.dest]['min']),
max_value=int(parser.options[opt.dest]['max']),
value=dflt, **kwargs)
elif opt.widget == "DecimalField":
dflt = opt.default or 0.0
val = st.number_input(opt.dest,
min_value=float(parser.options[opt.dest]['min']),
max_value=float(parser.options[opt.dest]['max']),
format="%g",
step=float(parser.options[opt.dest]['increment']),
value=dflt, **kwargs)
else:
st.error(f"Unknown: {opt.widget}")
val = dflt
elif opt.choices:
choices = list(opt.choices)
val = st.selectbox(opt.dest, index=choices.index(dflt), options=choices,
**kwargs)
else:
val = st.text_input(opt.dest, value=dflt, **kwargs)
if val != dflt:
opts[opt.dest] = val
elif isinstance(opt, _SubParsersAction):
if opt.dest == SUPPRESS:
k = st.sidebar.radio(opt.help,
options=sorted(set(opt.choices) - {'completion'}),
key=f"{key_prefix}{opt.dest}")
else:
k = st.sidebar.radio(opt.dest,
options=sorted(set(opt.choices) - {'completion'}),
**kwargs)
recurse(opt.choices[k], f"{key_prefix}{k.replace('_', ' ')}_")
else:
st.warning(f"Unknown option type:{opt}")

recurse(parser)
st.sidebar.image(str(THIS / "config_icon.png"))

parser = opts.pop(PARSER)
left, right = st.columns([1, 2])
with left:
st.write("**Command**")
with right:
prefix = st.checkbox("Prefix")
cmd = [Path(sys.executable).resolve().name, "-m", parser.prog] + [
(f"--{k.replace('_', '-')}"
if v is True else f"--{k.replace('_', '-')}={shlex.quote(str(v))}")
for k, v in opts.items()]
st.code(" ".join(cmd if prefix else cmd[2:]), "shell")
dry_run = not st.button("Run")
if dry_run:
log.debug(opts)
elif 'main__' in parser._defaults: # Cmd
with st.spinner("Running"):
st.write(parser._defaults['main__'](cmd[3:], verify_args=False))
elif 'run__' in parser._defaults: # Func
with st.spinner("Running"):
st.write(parser._defaults['run__'](**opts))
else:
st.error("Unknown action")


if __name__ == "__main__":
main()
74 changes: 39 additions & 35 deletions amypet/gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@ def patch_argument_kwargs(kwargs, gooey=True):
if 'help' in kwargs:
kwargs['help'] = RE_PRECOLON.sub("", RE_DEFAULT.sub("", kwargs['help']))

default = kwargs.get('default', None)
if default in WIDGETS:
dflt = kwargs.get('default', None)
if dflt in WIDGETS:
if gooey:
kwargs['widget'] = default
kwargs['widget'] = dflt
kwargs['default'] = None
elif gooey:
typ = kwargs.get("type", None)
Expand Down Expand Up @@ -214,44 +214,22 @@ def __str__(self):
super(Func, self).__str__())


def fix_subparser(subparser, gui_mode=True):
subparser.add_argument(
"--dry-run",
action="store_true",
help="don't run command (implies print_command)" if gui_mode else SUPPRESS,
)
return subparser


def print_not_none(value, **kwargs):
if value is not None:
print(value, **kwargs)


# progress_regex="^\s*(?P<percent>\d[.\d]*)%|",
# progress_expr="float(percent or 0)",
# hide_progress_msg=True,
# richtext_controls=True,
@Gooey(default_size=(768, 768), program_name="amypet", sidebar_title="pipeline",
image_dir=resource_filename(__name__, ""), show_restart_button=False,
header_bg_color="#ffffff", sidebar_bg_color="#a3b5cd", body_bg_color="#a3b5cd",
footer_bg_color="#2a569f", terminal_font_family="monospace", menu=[{
"name": "Help", "items": [{
"type": "Link", "menuTitle": "🌐 View source (online)",
"url": "https://github.com/AMYPAD/amypet"}, {
"type": "AboutDialog", "menuTitle": "🔍 About", "name": "AmyPET Pipeline",
"description": "GUI to run AmyPET tools", "version": __version__,
"copyright": "2021", "website": "https://amypad.eu",
"developer": "https://github.com/AMYPAD", "license": __licence__}]}])
def main(args=None, gui_mode=True):
logging.basicConfig(level=logging.INFO)
def get_main_parser(gui_mode=True, argparser=MyParser):
import miutil.cuinfo
import niftypad.api
import niftypad.models

from amypet import centiloid, dcm2nii, imscroll, imtrimup

parser = fix_subparser(MyParser(prog=None if gui_mode else "amypet"), gui_mode=gui_mode)
def fix_subparser(subparser, gui_mode=gui_mode):
subparser.add_argument(
"--dry-run",
action="store_true",
help="don't run command (implies print_command)" if gui_mode else SUPPRESS,
)
return subparser

parser = fix_subparser(argparser(prog=None if gui_mode else "amypet"), gui_mode=gui_mode)
sub_kwargs = {}
if sys.version_info[:2] >= (3, 7):
sub_kwargs["required"] = True
Expand Down Expand Up @@ -316,12 +294,38 @@ def argparser(prog, description=None, epilog=None, formatter_class=None):
Cmd([sys.executable, "-m", "miutil.cuinfo"], miutil.cuinfo.__doc__, version=miutil.__version__,
python_deps=["miutil[cuda]>=0.8.0"], argparser=argparser)

return parser


# progress_regex="^\s*(?P<percent>\d[.\d]*)%|",
# progress_expr="float(percent or 0)",
# hide_progress_msg=True,
# richtext_controls=True,
@Gooey(default_size=(768, 768), program_name="amypet", sidebar_title="pipeline",
image_dir=resource_filename(__name__, ""), show_restart_button=False,
header_bg_color="#ffffff", sidebar_bg_color="#a3b5cd", body_bg_color="#a3b5cd",
footer_bg_color="#2a569f", terminal_font_family="monospace", menu=[{
"name": "Help", "items": [{
"type": "Link", "menuTitle": "🌐 View source (online)",
"url": "https://github.com/AMYPAD/AmyPET"}, {
"type": "AboutDialog", "menuTitle": "🔍 About", "name": "AmyPET Pipeline",
"description": "GUI to run AmyPET tools", "version": __version__,
"copyright": "2021", "website": "https://amypad.eu",
"developer": "https://github.com/AMYPAD", "license": __licence__}]}])
def main(args=None, gui_mode=True):
logging.basicConfig(level=logging.INFO)
parser = get_main_parser(gui_mode=gui_mode)

if args is None:
args = sys.argv[1:]
args = [i for i in args if i not in ("--ignore-gooey",)]
opts = parser.parse_args(args=args)
args = [i for i in args if i not in ("--dry-run",)] # strip args

def print_not_none(value, **kwargs):
if value is not None:
print(value, **kwargs)

if gui_mode:
print(" ".join([Path(sys.executable).name, "-m amypet"] + args))
if opts.dry_run:
Expand Down
17 changes: 17 additions & 0 deletions amypet/web.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from streamlit.bootstrap import load_config_options, run

from . import _backend_web

CONFIG = {
'browser.gatherUsageStats': False, 'theme.base': 'light', 'theme.primaryColor': '#2a569f',
'theme.secondaryBackgroundColor': '#a3b5cd', 'theme.textColor': '#000000',
'theme.font': 'monospace'}


def main():
load_config_options(CONFIG)
run(_backend_web.__file__, "", [], CONFIG)


if __name__ == "__main__":
main()
8 changes: 5 additions & 3 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ license=MPL 2.0
license_file=LICENCE.md
url=https://amypad.eu
project_urls=
Repository=https://github.com/AMYPAD/amypet
Changelog=https://github.com/AMYPAD/amypet/releases
Documentation=https://github.com/AMYPAD/amypet/#amypet
Repository=https://github.com/AMYPAD/AmyPET
Changelog=https://github.com/AMYPAD/AmyPET/releases
Documentation=https://github.com/AMYPAD/AmyPET/#AmyPET
maintainer=Casper da Costa-Luis
[email protected]
keywords=pet, alzheimers
Expand Down Expand Up @@ -58,11 +58,13 @@ dev=
pytest-timeout
pytest-xdist
gui=Gooey>=1.0.8
web=streamlit>=0.85.2
niftypet=niftypet>=0.0.1
[options.entry_points]
console_scripts=
amypet=amypet.cli:main
amypet.gui=amypet.gui:main
amypet.web=amypet.web:main
[options.packages.find]
exclude=tests
[options.package_data]
Expand Down

0 comments on commit e7b727f

Please sign in to comment.