diff --git a/GUI.bat b/GUI.bat new file mode 100644 index 0000000..99bedb6 --- /dev/null +++ b/GUI.bat @@ -0,0 +1,7 @@ +@echo off + +python appia.py + +if %ERRORLEVEL%==1 ( + PAUSE +) \ No newline at end of file diff --git a/README.md b/README.md index e60d6cd..d348688 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,11 @@ to install R and RStudio. 4. *(Optional) Install [R Studio](https://www.rstudio.com/) for easier use of the manual plot scripts* +## GUI +Appia has a GUI built using [Gooey](https://github.com/chriskiehl/Gooey) which can +be launched by running `appia` with no arguments. If you wish to use the command +line interface instead include `--ignore-gooey`. + ## HPLC Processing Appia reads `.arw` or `.asc` files (from Waters and Shimadzu HPLCs, respectively) for information about the sample and how it was run, then optionally collects all the indicated diff --git a/appia.py b/appia.py index 5e6f7b0..5b56f75 100644 --- a/appia.py +++ b/appia.py @@ -1,21 +1,40 @@ #!/usr/bin/env python3 import argparse import logging +from gooey import Gooey, GooeyParser from parsers.process_parser import parser as process_parser from parsers.database_parser import parser as db_parser -main_parser = argparse.ArgumentParser( +main_parser = GooeyParser( description = 'Process chromatography data and visualize it on the web.' ) subparsers = main_parser.add_subparsers() -main_parser.add_argument( +verbosity = main_parser.add_argument_group('Verbosity') +vxg = verbosity.add_mutually_exclusive_group() +vxg.add_argument( + '-q', '--quiet', + help = 'Print Errors only', + action = 'store_const', + dest = 'verbosity', + const = 'q' +) +vxg.add_argument( '-v', '--verbose', - help = 'Get more informational messages', - action = 'count', - default = 0 + help = 'Print Info, Warnings, and Errors. Default state.', + action = 'store_const', + dest = 'verbosity', + const = 'v' +) +vxg.add_argument( + '--debug', + help = 'Print debug output.', + action = 'store_const', + dest = 'verbosity', + const = 'd' ) + subparsers.add_parser( name = 'process', help = 'Process data', @@ -27,11 +46,24 @@ parents = [db_parser] ) +@Gooey( + program_name = 'Appia', + default_size = (1080,720) +) def main(): args = main_parser.parse_args() - levels = [logging.WARNING, logging.INFO, logging.DEBUG] - level = levels[min(len(levels) - 1, args.verbose)] + levels = { + 'q': logging.ERROR, + 'v': logging.INFO, + 'd': logging.DEBUG + } + try: + level = levels[args.verbosity] + except KeyError: + level = logging.INFO + print(level) + logging.basicConfig( level = level, format = '{levelname}: {message} ({filename})', diff --git a/parsers/database_parser.py b/parsers/database_parser.py index 53c275f..6dbb157 100644 --- a/parsers/database_parser.py +++ b/parsers/database_parser.py @@ -1,4 +1,5 @@ import argparse +from gooey import GooeyParser from processors.database import Database, Config from processors.core import three_column_print @@ -26,7 +27,7 @@ def main(args): exp = db.pull_experiment(id) exp.save_csvs('.') -parser = argparse.ArgumentParser( +parser = GooeyParser( description = 'Database management', add_help=False ) @@ -34,7 +35,8 @@ def main(args): parser.add_argument( 'config', help = 'Config JSON file', - type = str + type = str, + widget = 'FileChooser' ) parser.add_argument( '-l', '--list', diff --git a/parsers/process_parser.py b/parsers/process_parser.py index c7f48ac..f45b480 100644 --- a/parsers/process_parser.py +++ b/parsers/process_parser.py @@ -4,6 +4,7 @@ import logging import subprocess import shutil +from gooey import GooeyParser from processors import hplc, fplc, experiment, core from processors.database import Database, Config from plotters import auto_plot @@ -165,111 +166,122 @@ def main(args): ) -parser = argparse.ArgumentParser( +parser = GooeyParser( description = 'Process chromatography data', add_help = False ) parser.set_defaults(func = main) +file_io = parser.add_argument_group('File IO') + parser.add_argument( 'files', - default = os.path.join(os.getcwd(), '*'), help = 'Glob or globs to find data files. For instance, "traces/*.arw"', - nargs = '+' + nargs = '+', + widget = 'MultiFileChooser' ) -parser.add_argument( +file_io.add_argument( '-i', '--id', help = 'Experiment ID. Default to name of HPLC Sample Set (Waters over Shimadzu, if present) or FPLC file name.', type = str ) -parser.add_argument( +file_io.add_argument( '-o', '--output-dir', - help = 'Directory in which to save CSVs and plots. Default makes a new dir with experiment name.' + help = 'Directory in which to save CSVs and plots. Default makes a new dir with experiment name.', + widget = 'DirChooser' ) -parser.add_argument( - '-r', '--reduce', - help = 'Reduce web HPLC data points to this many total. Default 1000. CSV files are saved at full temporal resolution regardless.', - type = int, - default = 1000 +file_io.add_argument( + '-k', '--no-move', + help = 'Process data files in place (do not move to new directory)', + action = 'store_true', + default = False ) -parser.add_argument( - '-d', '--database', - help = '''Upload experiment to couchdb. Optionally, provide config file location. -Default config location is "config.json" in appia directory. -Pass "env" to pull from environment variables.''', - dest = 'config', - nargs = '?', - const = os.path.join(os.path.dirname(os.path.realpath(__file__)), '..', 'config.json') +file_io.add_argument( + '-c', '--copy-manual', + help = 'Copy R template file for manual plot editing. Argument is directory relative to Appia root in which templates reside.', + nargs = '?', + const = 'plotters' ) -parser.add_argument( +file_io.add_argument( + '-s', '--post-to-slack', + help = "Send completed plots to Slack. Need a config JSON with slack token and channel.", + nargs = '?', + const = os.path.join(os.path.dirname(os.path.realpath(__file__)), '..', 'config.json'), + widget = 'FileChooser' +) + +process_args = parser.add_argument_group('Processing Options') +process_args.add_argument( '--hplc-flow-rate', help = 'Manually override flow rate. Provide a single number in mL/min', type = float ) -parser.add_argument( +process_args.add_argument( '--fplc-cv', help = 'Column volume for FPLC data. Default is 24 mL (GE/Cytiva 10/300 column).', type = int, default = 24 ) -parser.add_argument( - '--overwrite', - help = 'Overwrite database copy of experiment with same name without asking', - action = 'store_true' -) -parser.add_argument( +process_args.add_argument( '-n', '--normalize', help = 'Set maximum of this range (in mL) to 1', nargs = 2, type = float, default = [0.5, 1000] ) -parser.add_argument( +process_args.add_argument( '--strict-normalize', help = 'Also set minimum of normalization range to 0', action = 'store_true', default = False ) -parser.add_argument( - '-k', '--no-move', - help = 'Process data files in place (do not move to new directory)', - action = 'store_true', - default = False -) -parser.add_argument( +process_args.add_argument( '--channel-mapping', nargs = '+', default = ['A', 'Trp', 'B', 'GFP'], - help = 'Channel mappings for Shimadzu instruments. Default: A Trp B GFP' + help = 'Channel mappings for old Shimadzu instruments. Default: A Trp B GFP' ) -parser.add_argument( - '-c', '--copy-manual', - help = 'Copy R template file for manual plot editing. Argument is directory relative to Appia root in which templates reside. No argument uses default `plotters/`.', - nargs = '?', - const = 'plotters' + +web_up = parser.add_argument_group('Web Upload') + +web_up.add_argument( + '-r', '--reduce', + help = 'Reduce web HPLC data points to this many total. Default 1000. CSV files are saved at full temporal resolution regardless.', + type = int, + default = 1000 ) -parser.add_argument( +web_up.add_argument( + '-d', '--database', + help = '''Upload experiment to couchdb. Optionally, provide config file location. Default config location is "config.json" in appia directory. Enter "env" to use environment variables instead.''', + dest = 'config', + nargs = '?', + const = os.path.join(os.path.dirname(os.path.realpath(__file__)), '..', 'config.json'), + widget = 'FileChooser' +) +web_up.add_argument( + '--overwrite', + help = 'Overwrite database copy of experiment with same name without asking', + action = 'store_true' +) + +auto_plots = parser.add_argument_group('Auto Plots') +auto_plots.add_argument( '-p', '--plots', help = 'Make default plots', action = 'store_true', default = False ) -parser.add_argument( +auto_plots.add_argument( '-f', '--fractions', nargs = '+', default = None, - help = 'SEC fractions to fill in. Default is none. Giving two numbers will fill all fractions between those, inclusive. `auto` sets no limit for that side, e.g., -f auto auto fills all fractions. A third N will fill every Nth fraction, e.g., 2 for every other.' + help = 'SEC fractions to fill in. Default is none. Giving two numbers fills inclusive range; a third sets interval. E.g., 2 10 2 fills even fractions between 2 and 10.' ) -parser.add_argument( +auto_plots.add_argument( '-m', '--ml', nargs = 2, default = ['5', '25'], type = str, help = 'Inclusive range for auto-plot x-axis, in mL. Default is 5 to 25. To auto-set one limit, type `auto` instead of a number.' ) -parser.add_argument( - '-s', '--post-to-slack', - help = "Send completed plots to Slack. Need a config JSON with slack token and channel.", - nargs = '?', - const = os.path.join(os.path.dirname(os.path.realpath(__file__)), '..', 'config.json') -) \ No newline at end of file + diff --git a/process-and-rename.bat b/process-and-rename.bat index 7538316..d773810 100644 --- a/process-and-rename.bat +++ b/process-and-rename.bat @@ -2,7 +2,7 @@ set /P new_name="Rename experiment to: " -python appia.py -v process ./* --id "%new_name%" --output-dir "%new_name%" --database +python appia.py --ignore-gooey process ./* --id "%new_name%" --output-dir "%new_name%" --database if %ERRORLEVEL%==1 ( PAUSE diff --git a/process.bat b/process.bat index e185c17..07fc413 100644 --- a/process.bat +++ b/process.bat @@ -1,6 +1,6 @@ @echo off -python appia.py -v process ./* --database +python appia.py --ignore-gooey process ./* --database if %ERRORLEVEL%==1 ( PAUSE diff --git a/processors/experiment.py b/processors/experiment.py index df547d2..518fb32 100644 --- a/processors/experiment.py +++ b/processors/experiment.py @@ -129,9 +129,13 @@ def reduction_factor(df, num_ponts): total_points = df.shape[0] reduction_factor = floor(total_points/num_points) return df[::reduction_factor] - try: - self.hplc = self.hplc.groupby(['Channel', 'Sample', 'Normalization']).apply(lambda x: reduction_factor(x, num_points)) + self.hplc = self.hplc.groupby( + ['Channel', 'Sample', 'Normalization'], + as_index = False + ).apply( + lambda x: reduction_factor(x, num_points) + ) self.hplc = self.hplc.reset_index(drop = True) except AttributeError: return diff --git a/requirements.txt b/requirements.txt index c2d9119..63fc3be 100644 Binary files a/requirements.txt and b/requirements.txt differ diff --git a/web.py b/web.py index 752eedb..3c394e1 100644 --- a/web.py +++ b/web.py @@ -1,8 +1,8 @@ import logging import dash from dash import dependencies -import dash_core_components as dcc -import dash_html_components as html +from dash import dcc +from dash import html import plotly.express as px import plotly.graph_objects as go from urllib.parse import parse_qs