# -*- coding: utf-8 -*-
"""
The :mod:`neurotic.scripts` module handles starting the app from the command
line. It also provides a convenience function for quickly launching the app
using minimal metadata or an existing Neo :class:`Block <neo.core.Block>`, and
another for starting a Jupyter server with an example notebook.
.. autofunction:: quick_launch
.. autofunction:: launch_example_notebook
"""
import os
import sys
import argparse
import subprocess
import pkg_resources
from ephyviewer import QT, mkQApp
from . import __version__, global_config, _global_config_factory_defaults, global_config_file, default_log_level
from .datasets.data import load_dataset
from .gui.config import EphyviewerConfigurator, available_themes, available_ui_scales
from .gui.standalone import MainWindow
import logging
logger = logging.getLogger(__name__)
def parse_args(argv):
"""
"""
description = """
neurotic lets you curate, visualize, annotate, and share your behavioral
ephys data.
"""
epilog = f"""
Defaults for arguments and options can be changed in a global config file,
{os.path.relpath(global_config_file, os.path.expanduser('~'))}, located in
your home directory.
"""
parser = argparse.ArgumentParser(description=description, epilog=epilog)
defaults = global_config['defaults']
parser.set_defaults(**defaults)
parser.add_argument('file', nargs='?',
help='the path to a metadata YAML file'
f' (default: {"an example file" if defaults["file"] in [False, "example"] else defaults["file"] + "; to force the example, use: example"})')
parser.add_argument('dataset', nargs='?',
help='the name of a dataset in the metadata file to '
'select initially'
f' (default: {"the first entry in the metadata file" if defaults["dataset"] in [False, "first"] else defaults["dataset"] + "; to force the first, use: - first"})')
parser.add_argument('-V', '--version',
action='version',
version='neurotic {}'.format(__version__))
group = parser.add_mutually_exclusive_group()
group.add_argument('--debug', dest='debug',
action='store_true',
help='enable detailed log messages for debugging'
f'{" (default)" if defaults["debug"] else ""}')
group.add_argument('--no-debug', dest='debug',
action='store_false',
help='disable detailed log messages for debugging'
f'{" (default)" if not defaults["debug"] else ""}')
group = parser.add_mutually_exclusive_group()
group.add_argument('--lazy', dest='lazy',
action='store_true',
help='enable fast loading'
f'{" (default)" if defaults["lazy"] else ""}')
group.add_argument('--no-lazy', dest='lazy',
action='store_false',
help='disable fast loading'
f'{" (default)" if not defaults["lazy"] else ""}')
group = parser.add_mutually_exclusive_group()
group.add_argument('--thick-traces', dest='thick_traces',
action='store_true',
help='enable support for traces with thick lines, '
'which has a performance cost'
f'{" (default)" if defaults["thick_traces"] else ""}')
group.add_argument('--no-thick-traces', dest='thick_traces',
action='store_false',
help='disable support for traces with thick lines'
f'{" (default)" if not defaults["thick_traces"] else ""}')
group = parser.add_mutually_exclusive_group()
group.add_argument('--show-datetime', dest='show_datetime',
action='store_true',
help='display the real-world date and time, which '
'may be inaccurate depending on file type and '
'acquisition software'
f'{" (default)" if defaults["show_datetime"] else ""}')
group.add_argument('--no-show-datetime', dest='show_datetime',
action='store_false',
help='do not display the real-world date and time'
f'{" (default)" if not defaults["show_datetime"] else ""}')
parser.add_argument('--ui-scale', dest='ui_scale',
choices=available_ui_scales,
help='the scale of user interface elements, such as '
'text'
f' (default: {defaults["ui_scale"]})')
parser.add_argument('--theme', dest='theme',
choices=available_themes,
help='a color theme for the GUI'
f' (default: {defaults["theme"]})')
parser.add_argument('--use-factory-defaults',
action='store_true',
help='start with "factory default" settings, ignoring '
'other args and your global config file')
group = parser.add_argument_group('alternative modes')
group.add_argument('--launch-example-notebook',
action='store_true',
help='launch Jupyter with an example notebook '
'instead of starting the standalone app (other '
'args will be ignored)')
args = parser.parse_args(argv[1:])
if args.use_factory_defaults:
# replace every argument with the factory default
for k, v in _global_config_factory_defaults['defaults'].items():
setattr(args, k, v)
# this special value for the first positional argument can be used to skip
# specifying any file and instead use the default file
if args.file == '-':
args.file = defaults['file']
# these special values for the positional arguments can be used to override
# the defaults set in the global config file with the defaults normally set
# in the absence of global config file settings
if args.file == 'example':
args.file = _global_config_factory_defaults['defaults']['file']
if args.dataset == 'first':
args.dataset = _global_config_factory_defaults['defaults']['dataset']
if args.debug:
logger.parent.setLevel(logging.DEBUG)
# lower the threshold for PyAV messages printed to the console from
# critical to warning
logging.getLogger('libav').setLevel(logging.WARNING)
if not args.launch_example_notebook:
# show only if Jupyter won't be launched, since the setting will
# not carry over into the kernel started by Jupyter
logger.debug('Debug messages enabled')
else:
# this should only be necessary if parse_args is called with --no-debug
# after having previously enabled debug messages in the current session
# (such as during unit testing)
logger.parent.setLevel(default_log_level)
# raise the threshold for PyAV messages printed to the console from
# warning to critical
logging.getLogger('libav').setLevel(logging.CRITICAL)
logger.debug(f'Global config: {global_config}')
logger.debug(f'Parsed arguments: {args}')
return args
def win_from_args(args):
"""
"""
win = MainWindow(file=args.file, initial_selection=args.dataset,
lazy=args.lazy, theme=args.theme, ui_scale=args.ui_scale,
support_increased_line_width=args.thick_traces,
show_datetime=args.show_datetime)
return win
[docs]def launch_example_notebook():
"""
Start a Jupyter server and open the example notebook.
"""
path = pkg_resources.resource_filename('neurotic',
'example/example-notebook.ipynb')
out = None
# check whether Jupyter is installed
try:
out = subprocess.Popen(['jupyter', 'notebook', '--version'],
stdout=subprocess.PIPE).communicate()[0]
except FileNotFoundError as e:
logger.error('Unable to verify Jupyter is installed using "jupyter '
'notebook --version". Is it installed?')
if out:
# run Jupyter on the example notebook
try:
out = subprocess.Popen(['jupyter', 'notebook', path],
stdout=subprocess.PIPE).communicate()[0]
except FileNotFoundError as e:
logger.error(f'Unable to locate the example notebook at {path}')
def main():
"""
"""
args = parse_args(sys.argv)
if args.launch_example_notebook:
launch_example_notebook()
else:
logger.info('Loading user interface')
app = mkQApp()
splash = QT.QSplashScreen(QT.QPixmap(':/neurotic-logo-300.png'))
splash.show()
win = win_from_args(args)
win.show()
splash.finish(win)
logger.info('Ready')
app.exec_()
[docs]def quick_launch(metadata={}, blk=None, lazy=True):
"""
Load data, configure the GUI, and launch the app with one convenient
function.
This function allows *neurotic* to be used easily in interactive sessions
and scripts. For example, dictionaries can be passed as metadata:
>>> metadata = {'data_file': 'data.axgx'}
>>> neurotic.quick_launch(metadata=metadata)
An existing Neo :class:`Block <neo.core.Block>` can be passed directly:
>>> neurotic.quick_launch(blk=my_neo_block)
This function is equivalent to the following:
>>> blk = load_dataset(metadata, blk, lazy=lazy)
>>> ephyviewer_config = EphyviewerConfigurator(metadata, blk, lazy=lazy)
>>> ephyviewer_config.show_all()
>>> ephyviewer_config.launch_ephyviewer()
"""
# make sure this matches the docstring after making changes
blk = load_dataset(metadata, blk, lazy=lazy)
ephyviewer_config = EphyviewerConfigurator(metadata, blk, lazy=lazy)
ephyviewer_config.show_all()
ephyviewer_config.launch_ephyviewer()