oceanproc 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- oceanproc/__init__.py +1 -0
- oceanproc/bids_wrapper.py +331 -0
- oceanproc/events_long.py +153 -0
- oceanproc/firstlevel/__init__.py +0 -0
- oceanproc/firstlevel/run_glm.py +923 -0
- oceanproc/firstlevel/tests/__init__.py +0 -0
- oceanproc/firstlevel/tests/test_run_glm.py +165 -0
- oceanproc/fmriprep_wrapper.py +222 -0
- oceanproc/group_series.py +228 -0
- oceanproc/grouplevel/__init__.py +0 -0
- oceanproc/grouplevel/computations.py +80 -0
- oceanproc/grouplevel/design_mat_utils.py +54 -0
- oceanproc/grouplevel/dscalar_utils.py +51 -0
- oceanproc/grouplevel/run.py +18 -0
- oceanproc/grouplevel/utils.py +4 -0
- oceanproc/ocean_proc.py +215 -0
- oceanproc/oceanparse.py +107 -0
- oceanproc/utils.py +224 -0
- oceanproc/web/TODO.md +4 -0
- oceanproc/web/__init__.py +0 -0
- oceanproc/web/app.py +78 -0
- oceanproc/web/config.json +7 -0
- oceanproc/web/get_parser_json.py +58 -0
- oceanproc/web/static/components/ArgparseForm.js +9 -0
- oceanproc/web/static/components/ArgparseFormEntry.js +52 -0
- oceanproc/web/static/components/DirIcon.js +11 -0
- oceanproc/web/static/components/FileBrowser.js +85 -0
- oceanproc/web/static/components/FileBrowserOpenBtn.js +12 -0
- oceanproc/web/static/components/FileEntry.js +32 -0
- oceanproc/web/static/components/FileIcon.js +11 -0
- oceanproc/web/static/components/OceanprocForm.js +53 -0
- oceanproc/web/static/components/styles.css +44 -0
- oceanproc/web/templates/filesystem.html +13 -0
- oceanproc/web/templates/filesystem_fail.html +2 -0
- oceanproc/web/templates/oceanproc_form.html +23 -0
- oceanproc/web/templates/react-test.html +0 -0
- oceanproc/web/templates/username_card_component_test.html +39 -0
- oceanproc-0.1.0.dist-info/METADATA +29 -0
- oceanproc-0.1.0.dist-info/RECORD +41 -0
- oceanproc-0.1.0.dist-info/WHEEL +4 -0
- oceanproc-0.1.0.dist-info/entry_points.txt +4 -0
oceanproc/utils.py
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import logging
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import json
|
|
5
|
+
from types import SimpleNamespace
|
|
6
|
+
|
|
7
|
+
logger = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
default_log_format = "%(levelname)s:%(asctime)s:%(module)s: %(message)s"
|
|
10
|
+
|
|
11
|
+
flags = SimpleNamespace(debug = False)
|
|
12
|
+
|
|
13
|
+
def takes_arguments(decorator):
|
|
14
|
+
"""
|
|
15
|
+
A meta-decorator to use on decorators that take in other
|
|
16
|
+
arguments than just the function they are applied to
|
|
17
|
+
"""
|
|
18
|
+
def wrapper(*args, **kwargs):
|
|
19
|
+
def replacement(func):
|
|
20
|
+
return decorator(func, *args, **kwargs)
|
|
21
|
+
return replacement
|
|
22
|
+
return wrapper
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def debug_logging(func):
|
|
26
|
+
"""
|
|
27
|
+
A decorator function that debug logs a function call
|
|
28
|
+
and the arguments used in the call. Can log with a
|
|
29
|
+
specified logger or this module's logger if one is
|
|
30
|
+
not supplied
|
|
31
|
+
|
|
32
|
+
:param func: function this decorator is applied to
|
|
33
|
+
:type func: function
|
|
34
|
+
:param this_logger: a logger to use for logging the function call
|
|
35
|
+
:type this_logger: logging.Logger object
|
|
36
|
+
:return: a function that is the input function wrapped by this decorator
|
|
37
|
+
:rtype: function
|
|
38
|
+
"""
|
|
39
|
+
def inner(*args, **kwargs):
|
|
40
|
+
log_linebreak()
|
|
41
|
+
logger.debug(
|
|
42
|
+
f"calling - {func.__module__}:{func.__name__}({', '.join([str(a) for a in args] + [f'{k}={v}' for k,v in kwargs.items()])})\n"
|
|
43
|
+
)
|
|
44
|
+
return func(*args, **kwargs)
|
|
45
|
+
return inner
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def exit_program_early(msg:str,
|
|
49
|
+
exit_func=None):
|
|
50
|
+
"""
|
|
51
|
+
Exit the program while printing parameter 'msg'. If an exit
|
|
52
|
+
function is sepcified, it will be called before the program
|
|
53
|
+
exits
|
|
54
|
+
|
|
55
|
+
:param msg: error message to display.
|
|
56
|
+
:type msg: str
|
|
57
|
+
:param exit_func: function to be called before program exit (no required arguments)
|
|
58
|
+
:type exit_func: function
|
|
59
|
+
|
|
60
|
+
"""
|
|
61
|
+
log_linebreak()
|
|
62
|
+
logger.error(f"---[ERROR]: {msg} \nExiting the program now...\n")
|
|
63
|
+
if exit_func and callable(exit_func):
|
|
64
|
+
exit_func()
|
|
65
|
+
sys.exit(1)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def prompt_user_continue(msg:str) -> bool:
|
|
69
|
+
"""
|
|
70
|
+
Prompt the user to continue with a custom message.
|
|
71
|
+
|
|
72
|
+
:param msg: prompt message to display.
|
|
73
|
+
:type msg: str
|
|
74
|
+
|
|
75
|
+
"""
|
|
76
|
+
prompt_msg = f"{msg} \n\t---(press 'y' for yes, other input will mean no)"
|
|
77
|
+
user_continue = input(prompt_msg+"\n")
|
|
78
|
+
ans = (user_continue.lower() == "y")
|
|
79
|
+
logger.debug(f"User Prompt: {prompt_msg}")
|
|
80
|
+
logger.debug(f"User Response: {user_continue} ({ans})")
|
|
81
|
+
return ans
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def make_option(value,
|
|
85
|
+
key: str=None,
|
|
86
|
+
delimeter: str=" ",
|
|
87
|
+
convert_underscore: bool=False):
|
|
88
|
+
"""
|
|
89
|
+
Generate a string, representing an option that gets fed into a subprocess or script.
|
|
90
|
+
|
|
91
|
+
For example, if a key is 'option' and its value is True, the option string it will generate would be:
|
|
92
|
+
|
|
93
|
+
--option
|
|
94
|
+
|
|
95
|
+
If value is equal to some string 'value', then the string would be:
|
|
96
|
+
|
|
97
|
+
--option value
|
|
98
|
+
|
|
99
|
+
If value is a list of strings:
|
|
100
|
+
|
|
101
|
+
--option value1 value2 ... valuen
|
|
102
|
+
:param value: Value to pass in along with the 'key' param.
|
|
103
|
+
:type value: any
|
|
104
|
+
:param key: Name of option, without any hyphen at the beginning.
|
|
105
|
+
:type key: str
|
|
106
|
+
:param delimeter: character to separate the key and the value in the option string. Default is a space.
|
|
107
|
+
:type delimeter: str
|
|
108
|
+
:param convert_underscore: flag to indicate that underscores should be replaced with '-'
|
|
109
|
+
:type convert_underscore: bool
|
|
110
|
+
:return: String to pass as an option into a subprocess call.
|
|
111
|
+
:rtype: str
|
|
112
|
+
"""
|
|
113
|
+
second_part = None
|
|
114
|
+
if key and convert_underscore:
|
|
115
|
+
key = key.replace("_", "-")
|
|
116
|
+
if not value:
|
|
117
|
+
return ""
|
|
118
|
+
elif type(value) == bool and value:
|
|
119
|
+
second_part = " "
|
|
120
|
+
elif type(value) == list:
|
|
121
|
+
second_part = f"{delimeter}{' '.join([str(v) for v in value])}"
|
|
122
|
+
else:
|
|
123
|
+
second_part = f"{delimeter}{str(value)}"
|
|
124
|
+
return f"--{key}{second_part}" if key else second_part
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def add_file_handler(this_logger: logging.Logger,
|
|
128
|
+
log_path: Path,
|
|
129
|
+
format_str: str=default_log_format):
|
|
130
|
+
"""
|
|
131
|
+
Adds a file handler with the input file path and input message formatting to the input logger.
|
|
132
|
+
The default message formatting in defined at the top of this file.
|
|
133
|
+
|
|
134
|
+
:param this_logger: A logger object to add the file handler to
|
|
135
|
+
:type this_logger: logging.Logger object
|
|
136
|
+
:param log_path: A path to the output file for the file handler
|
|
137
|
+
:type log_path: pathlib.Path
|
|
138
|
+
:param format_str: a string representing the desired message formatting for the file_handler. (See logging.Formatter class for more detail)
|
|
139
|
+
:type format_str: str
|
|
140
|
+
"""
|
|
141
|
+
file_handler = logging.FileHandler(log_path)
|
|
142
|
+
file_handler.setFormatter(logging.Formatter(format_str))
|
|
143
|
+
this_logger.addHandler(file_handler)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def prepare_subprocess_logging(this_logger,
|
|
147
|
+
stop=False):
|
|
148
|
+
"""
|
|
149
|
+
Prepares a logger for piping subprocess outputs to its handlers. This function removes
|
|
150
|
+
newlines from all of the logger's handlers and simplifies the message format to cleaning
|
|
151
|
+
display subprocess outputs. If 'stop' is True, the logger handlers are restored to defaults
|
|
152
|
+
|
|
153
|
+
:param this_logger: A logger object to change formatting for
|
|
154
|
+
:type this_logger: logging.Logger object
|
|
155
|
+
:param stop: A flag to indicate if subprocessing logging should end
|
|
156
|
+
:type stop: bool
|
|
157
|
+
"""
|
|
158
|
+
if stop:
|
|
159
|
+
while this_logger != None:
|
|
160
|
+
for h in this_logger.handlers:
|
|
161
|
+
h.setFormatter(logging.Formatter(default_log_format))
|
|
162
|
+
h.terminator = "\n"
|
|
163
|
+
this_logger = this_logger.parent
|
|
164
|
+
else:
|
|
165
|
+
while this_logger != None:
|
|
166
|
+
for h in this_logger.handlers:
|
|
167
|
+
h.setFormatter(logging.Formatter("%(message)s"))
|
|
168
|
+
h.terminator = ""
|
|
169
|
+
this_logger = this_logger.parent
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def log_linebreak():
|
|
173
|
+
"""
|
|
174
|
+
Logs a single blank line using this module's logger.
|
|
175
|
+
Changes the formatter for each handler to be a empty, logs
|
|
176
|
+
a single blank line, then changes each formatter back to
|
|
177
|
+
the default format
|
|
178
|
+
"""
|
|
179
|
+
traverse_logger = logger
|
|
180
|
+
while traverse_logger != None:
|
|
181
|
+
for h in traverse_logger.handlers:
|
|
182
|
+
h.setFormatter(logging.Formatter(""))
|
|
183
|
+
traverse_logger = traverse_logger.parent
|
|
184
|
+
logger.info("")
|
|
185
|
+
traverse_logger = logger
|
|
186
|
+
while traverse_logger != None:
|
|
187
|
+
for h in traverse_logger.handlers:
|
|
188
|
+
h.setFormatter(logging.Formatter(default_log_format))
|
|
189
|
+
traverse_logger = traverse_logger.parent
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def export_args_to_file(args,
|
|
193
|
+
argument_group,
|
|
194
|
+
file_path: Path):
|
|
195
|
+
"""
|
|
196
|
+
Takes the arguments in the argument group, and exports their names and values in the 'args'
|
|
197
|
+
namespace to a file specified at 'file_path'. The input 'file_path' can either be a txt
|
|
198
|
+
file or a json file.
|
|
199
|
+
|
|
200
|
+
:param args: an argument namespace to pull input values from
|
|
201
|
+
:type args: argparse.Namespace
|
|
202
|
+
:param argument_group: The argument group representing the subset of inputs to save to a file
|
|
203
|
+
:type argument_group: argparse._ArgumentGroup
|
|
204
|
+
:param file_path: a path to a file where the arguments should be saved
|
|
205
|
+
:type file_path: pathlib.Path
|
|
206
|
+
"""
|
|
207
|
+
|
|
208
|
+
all_opts = dict(args._get_kwargs())
|
|
209
|
+
opts_to_save = dict()
|
|
210
|
+
for a in argument_group._group_actions:
|
|
211
|
+
if a.dest in all_opts and all_opts[a.dest]:
|
|
212
|
+
if type(all_opts[a.dest]) == bool:
|
|
213
|
+
opts_to_save[a.option_strings[0]] = ""
|
|
214
|
+
continue
|
|
215
|
+
elif isinstance(all_opts[a.dest], Path):
|
|
216
|
+
opts_to_save[a.option_strings[0]] = str(all_opts[a.dest])
|
|
217
|
+
continue
|
|
218
|
+
opts_to_save[a.option_strings[0]] = all_opts[a.dest]
|
|
219
|
+
with open(file_path, "w") as f:
|
|
220
|
+
if file_path.suffix == ".json":
|
|
221
|
+
f.write(json.dumps(opts_to_save, indent=4))
|
|
222
|
+
else:
|
|
223
|
+
for k,v in opts_to_save.items():
|
|
224
|
+
f.write(f"{k}{make_option(value=v)}\n")
|
oceanproc/web/TODO.md
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
- [ ] Add a file explorer for all of the path-related options
|
|
2
|
+
- [ ] Implement the POST request to begin an oceanproc instance with the formdata
|
|
3
|
+
- [ ] Style everything better
|
|
4
|
+
- [ ] Add method of tracking current oceanproc instances through the web frontend, brainstorm ways to do this
|
|
File without changes
|
oceanproc/web/app.py
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
import os
|
|
4
|
+
import re
|
|
5
|
+
from flask import Flask, render_template, request, jsonify
|
|
6
|
+
|
|
7
|
+
app = Flask(__name__)
|
|
8
|
+
root_dir = Path(os.environ['DATA1'])
|
|
9
|
+
|
|
10
|
+
@app.route('/')
|
|
11
|
+
def oceanproc():
|
|
12
|
+
return "<h1>Hello, world!</h1>"
|
|
13
|
+
|
|
14
|
+
@app.route('/files/')
|
|
15
|
+
@app.route('/files/<path:subpath>')
|
|
16
|
+
def get_filesystem(subpath=""):
|
|
17
|
+
app.logger.debug(f"path: {subpath}")
|
|
18
|
+
full_path = Path(os.path.realpath(f"{root_dir.as_posix()}/{subpath}"))
|
|
19
|
+
if not full_path.is_dir():
|
|
20
|
+
return render_template('filesystem_fail.html', nonexistant_path=full_path.as_posix())
|
|
21
|
+
dirs, files = [], []
|
|
22
|
+
for f in full_path.iterdir():
|
|
23
|
+
file_obj = {}
|
|
24
|
+
file_obj["isdir"] = True if f.is_dir() else False
|
|
25
|
+
file_obj["basename"] = f.name
|
|
26
|
+
file_obj["realpath"] = f.resolve().as_posix()
|
|
27
|
+
file_obj["pathfromroot"] = f.relative_to(root_dir)
|
|
28
|
+
dirs.append(file_obj) if f.is_dir() else files.append(file_obj)
|
|
29
|
+
dirs = sorted(dirs, key=lambda f: f['basename'])
|
|
30
|
+
files = sorted(files, key=lambda f: f['basename'])
|
|
31
|
+
file_list = dirs + files
|
|
32
|
+
if full_path != root_dir:
|
|
33
|
+
app.logger.info("here")
|
|
34
|
+
file_list.insert(0, {"isdir": True, "basename": "..", "realpath": full_path.as_posix(), "pathfromroot": re.sub(r'(.*)/.*', r'\1', full_path)})
|
|
35
|
+
return render_template('filesystem.html',
|
|
36
|
+
cur_dir=full_path,
|
|
37
|
+
file_list=file_list)
|
|
38
|
+
|
|
39
|
+
@app.route('/api/get_filesystem/', methods=['GET'])
|
|
40
|
+
@app.route('/api/get_filesystem/<path:subpath>', methods=['GET'])
|
|
41
|
+
def get_filesystem_json(subpath=""):
|
|
42
|
+
full_path = Path(os.path.realpath(f"{root_dir.as_posix()}/{subpath}"))
|
|
43
|
+
dirs, files = [], []
|
|
44
|
+
for f in full_path.iterdir():
|
|
45
|
+
file_obj = {}
|
|
46
|
+
file_obj["isdir"] = True if f.is_dir() else False
|
|
47
|
+
file_obj["basename"] = f.name
|
|
48
|
+
file_obj["realpath"] = f.resolve().as_posix()
|
|
49
|
+
file_obj["pathfromroot"] = f.relative_to(root_dir).as_posix()
|
|
50
|
+
dirs.append(file_obj) if f.is_dir() else files.append(file_obj)
|
|
51
|
+
dirs = sorted(dirs, key=lambda f: f['basename'])
|
|
52
|
+
files = sorted(files, key=lambda f: f['basename'])
|
|
53
|
+
file_list = dirs + files
|
|
54
|
+
if full_path != root_dir:
|
|
55
|
+
app.logger.info("here")
|
|
56
|
+
file_list.insert(0, {'isdir': True,
|
|
57
|
+
'basename': "..",
|
|
58
|
+
'realpath': full_path.resolve().as_posix(),
|
|
59
|
+
'pathfromroot': full_path.parents[0].relative_to(root_dir).as_posix()})
|
|
60
|
+
return jsonify({'cur_dir': full_path.as_posix(),
|
|
61
|
+
'file_list': file_list}), 200
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@app.route('/api/get_parser_args/', methods=['GET'])
|
|
65
|
+
def get_parser_args():
|
|
66
|
+
from .get_parser_json import generate_json_from_parser_options
|
|
67
|
+
parser_args = generate_json_from_parser_options()
|
|
68
|
+
return jsonify(parser_args), 200
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@app.route('/formtest/', methods=['GET'])
|
|
72
|
+
def formtest():
|
|
73
|
+
return render_template('oceanproc_form.html'), 200
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import ast
|
|
2
|
+
import asttokens
|
|
3
|
+
from html import escape
|
|
4
|
+
import codecs
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import re
|
|
8
|
+
import re
|
|
9
|
+
import subprocess
|
|
10
|
+
import sys
|
|
11
|
+
from textwrap import dedent
|
|
12
|
+
|
|
13
|
+
def _create_argument_dicts(path: str, parser_group_names: list[dict]):
|
|
14
|
+
def get_opt(node):
|
|
15
|
+
toklist = list(script_ast.get_tokens(node))
|
|
16
|
+
optname = [re.sub(r'\"--(.*)\"', r'\1', t.string) for t in toklist if
|
|
17
|
+
t.type == 3 and
|
|
18
|
+
('--' in t.string or '-' not in t.string)][0]
|
|
19
|
+
equalindices = [idx for idx in range(len(toklist)) if toklist[idx].string == "="]
|
|
20
|
+
optsettingkeys = [toklist[idx-1].string for idx in equalindices]
|
|
21
|
+
optsettingvals = []
|
|
22
|
+
for startidx in equalindices: # Generate string representing value of kwarg, even if the value is longer than one token
|
|
23
|
+
optsettingval = ""
|
|
24
|
+
idx = startidx + 1
|
|
25
|
+
while toklist[idx].string != "," and idx < len(toklist) - 1:
|
|
26
|
+
optsettingval += re.sub(r'^[\"\'](.*)[\"\']$', r'\1', toklist[idx].string) # Remove outer quotes in string repr defined by ast
|
|
27
|
+
idx += 1
|
|
28
|
+
optsettingvals.append(optsettingval)
|
|
29
|
+
option = {optname: {k: v for (k, v) in zip(optsettingkeys, optsettingvals)}}
|
|
30
|
+
return option
|
|
31
|
+
arguments = []
|
|
32
|
+
with open(path) as f:
|
|
33
|
+
script_ast = asttokens.ASTTokens(f.read(), parse=True)
|
|
34
|
+
for node in ast.walk(script_ast.tree):
|
|
35
|
+
if isinstance(node, ast.Call):
|
|
36
|
+
if (isinstance(node.func, ast.Attribute) and
|
|
37
|
+
isinstance(node.func.value, ast.Name) and
|
|
38
|
+
node.func.value.id in parser_group_names and
|
|
39
|
+
isinstance(node.func.attr, str) and
|
|
40
|
+
node.func.attr == 'add_argument'):
|
|
41
|
+
arguments.append(get_opt(node))
|
|
42
|
+
return arguments
|
|
43
|
+
|
|
44
|
+
def generate_json_from_parser_options(config_path: str = 'config.json') -> list[dict]:
|
|
45
|
+
"""
|
|
46
|
+
Returns a list of Python dicts, which will be interpreted in JSON format by a React component for generating an HTML form corresponding to command-line arguments.
|
|
47
|
+
|
|
48
|
+
:return: A Python dict containing two fields: 'option', the name of the argparse option, and 'html_type', a string containing the corresponding HTML <input> type field.
|
|
49
|
+
:rtype: list[dict]
|
|
50
|
+
"""
|
|
51
|
+
with open(config_path) as f:
|
|
52
|
+
config = json.load(f)
|
|
53
|
+
options_script_path = config["options_script_path"]
|
|
54
|
+
if not os.path.isfile(options_script_path):
|
|
55
|
+
raise RuntimeError(f"ERROR: script at {options_script_path} does not exist.")
|
|
56
|
+
parser_group_names = config["parser_group_names"]
|
|
57
|
+
argument_dicts = _create_argument_dicts(options_script_path, parser_group_names)
|
|
58
|
+
return argument_dicts
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { html } from 'htm/preact';
|
|
2
|
+
import { render } from 'preact';
|
|
3
|
+
import { useState, useEffect, useRef, useMemo } from 'preact/hooks';
|
|
4
|
+
import { FileBrowser } from './FileBrowser.js';
|
|
5
|
+
|
|
6
|
+
export function ArgparseForm(props) {
|
|
7
|
+
function sortByInputType(a, b){
|
|
8
|
+
}
|
|
9
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { html } from 'htm/preact';
|
|
2
|
+
import { useState, useEffect, useRef, useMemo } from 'preact/hooks';
|
|
3
|
+
import { FileBrowser } from './FileBrowser.js';
|
|
4
|
+
|
|
5
|
+
export function ArgparseFormEntry(props) {
|
|
6
|
+
const [optName, setOptName] = useState("")
|
|
7
|
+
const [optObj, setOptObj] = useState({})
|
|
8
|
+
const [optHtml, setOptHtml] = useState(html``)
|
|
9
|
+
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
setOptName(Object.keys(props.obj)[0])
|
|
12
|
+
setOptObj(props.obj[Object.keys(props.obj)[0]])
|
|
13
|
+
}, [])
|
|
14
|
+
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
switch (optObj.action) {
|
|
17
|
+
case "store_true":
|
|
18
|
+
setOptHtml(html`
|
|
19
|
+
<div class="form-check">
|
|
20
|
+
<input class="form-check-input" type=checkbox id="${optName}Input" name="${props.formName}"></input>
|
|
21
|
+
<label class="form-check-label" for=${optName}>${optName}</label>
|
|
22
|
+
</div>
|
|
23
|
+
`)
|
|
24
|
+
break;
|
|
25
|
+
default:
|
|
26
|
+
setOptHtml(html`
|
|
27
|
+
<div class="form-group argparseFormTextInput">
|
|
28
|
+
<label for=${optName}>${optName}</label>
|
|
29
|
+
<input class="form-control" type=text id="${optName}Input" name="${props.formName}"></input>
|
|
30
|
+
</div>
|
|
31
|
+
`)
|
|
32
|
+
}
|
|
33
|
+
if (optObj.type !== undefined && optObj.type.includes("Path")) {
|
|
34
|
+
setOptHtml(html`
|
|
35
|
+
<div class="form-group argparseFormTextInput">
|
|
36
|
+
<p>${optName}</p>
|
|
37
|
+
<button type="button"
|
|
38
|
+
class="btn btn-primary"
|
|
39
|
+
data-toggle="modal"
|
|
40
|
+
data-target="#${optName}Modal">Select path for ${optName}</button>
|
|
41
|
+
<${FileBrowser}
|
|
42
|
+
modalId="${optName}Modal"
|
|
43
|
+
submitBtnText="Choose directory"
|
|
44
|
+
browserTitle="Select path for ${optName}"
|
|
45
|
+
handleSubmit=${(path) => console.log(path)}><//>
|
|
46
|
+
</div>
|
|
47
|
+
`)
|
|
48
|
+
}
|
|
49
|
+
}, [optObj])
|
|
50
|
+
return optHtml
|
|
51
|
+
}
|
|
52
|
+
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { html } from 'htm/preact';
|
|
2
|
+
import { render } from 'preact';
|
|
3
|
+
import { useState, useEffect, useRef } from 'preact/hooks';
|
|
4
|
+
|
|
5
|
+
export function DirIcon() {
|
|
6
|
+
return html`
|
|
7
|
+
<svg style="margin-right:8px;" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6" width="10px" height="10px">
|
|
8
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M2.25 12.75V12A2.25 2.25 0 0 1 4.5 9.75h15A2.25 2.25 0 0 1 21.75 12v.75m-8.69-6.44-2.12-2.12a1.5 1.5 0 0 0-1.061-.44H4.5A2.25 2.25 0 0 0 2.25 6v12a2.25 2.25 0 0 0 2.25 2.25h15A2.25 2.25 0 0 0 21.75 18V9a2.25 2.25 0 0 0-2.25-2.25h-5.379a1.5 1.5 0 0 1-1.06-.44Z" />
|
|
9
|
+
</svg>
|
|
10
|
+
`
|
|
11
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { html } from 'htm/preact';
|
|
2
|
+
import { render } from 'preact';
|
|
3
|
+
import { useState, useEffect, useRef } from 'preact/hooks';
|
|
4
|
+
import { FileEntry } from './FileEntry.js';
|
|
5
|
+
|
|
6
|
+
export function FileBrowser(props) {
|
|
7
|
+
const [filepaths, setFilepaths] = useState({})
|
|
8
|
+
const [changedDir, setChangedDir] = useState('')
|
|
9
|
+
const [curChosenDir, setCurChosenDir] = useState('')
|
|
10
|
+
const [submittedDir, setSubmittedDir] = useState('')
|
|
11
|
+
const buttonRef = useRef(null)
|
|
12
|
+
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
const xhr = new XMLHttpRequest();
|
|
15
|
+
xhr.open('GET', '/api/get_filesystem/')
|
|
16
|
+
xhr.onload = () => {
|
|
17
|
+
if (xhr.status === 200) {
|
|
18
|
+
setFilepaths(JSON.parse(xhr.responseText))
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
xhr.send()
|
|
22
|
+
}, [])
|
|
23
|
+
|
|
24
|
+
// Refresh on dir change
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
if (changedDir !== '') {
|
|
27
|
+
const xhr = new XMLHttpRequest();
|
|
28
|
+
xhr.open('GET', `/api/get_filesystem/${changedDir}`)
|
|
29
|
+
xhr.onload = () => {
|
|
30
|
+
if (xhr.status === 200) {
|
|
31
|
+
setFilepaths(JSON.parse(xhr.responseText))
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
xhr.send()
|
|
35
|
+
setChangedDir('')
|
|
36
|
+
setCurChosenDir('')
|
|
37
|
+
}
|
|
38
|
+
}, [changedDir])
|
|
39
|
+
|
|
40
|
+
// Enable submit button on chosen radio button
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
if (buttonRef.current !== null){
|
|
43
|
+
if (curChosenDir !== '') {
|
|
44
|
+
buttonRef.current.disabled = false
|
|
45
|
+
} else {
|
|
46
|
+
buttonRef.current.disabled = true
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}, [curChosenDir])
|
|
50
|
+
|
|
51
|
+
if (JSON.stringify(filepaths) !== '{}') {
|
|
52
|
+
return html`
|
|
53
|
+
<div class="modal fade" id="${props.modalId}" tabindex="-1" role="dialog" aria-labelledby="${props.modalId}Title" aria-hidden="true">
|
|
54
|
+
<div class="modal-dialog" role="document">
|
|
55
|
+
<div class="modal-content">
|
|
56
|
+
<div class="modal-header text-light bg-primary">
|
|
57
|
+
<h5 class="modal-title" id="${props.modalId}Title">${props.browserTitle}</h5>
|
|
58
|
+
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
|
59
|
+
<span aria-hidden="true">X</span>
|
|
60
|
+
</button>
|
|
61
|
+
</div>
|
|
62
|
+
<div class="modal-body text-light bg-dark">
|
|
63
|
+
<ul class="fileEntryList" >
|
|
64
|
+
${filepaths.file_list.map(file => html`
|
|
65
|
+
<${FileEntry} file=${file} key=${file} setChangedDir=${setChangedDir} setCurChosenDir=${setCurChosenDir}><//>
|
|
66
|
+
`)}
|
|
67
|
+
</ul>
|
|
68
|
+
</div>
|
|
69
|
+
<div class="modal-footer">
|
|
70
|
+
<button ref=${buttonRef} class="btn btn-primary" data-dismiss="modal" onClick=${() => props.handleSubmit(curChosenDir)} disabled>${props.submitBtnText}</button>
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
`
|
|
76
|
+
} else {
|
|
77
|
+
return html`
|
|
78
|
+
<div class="modal FileBrowserContainer" id="${props.modalId}" tabindex="-1" role="dialog" aria-labelledby="${props.modalId}Title" aria-hidden="true">
|
|
79
|
+
<div class="spinner-border" role="status" style="width: 10rem; height: 10rem;">
|
|
80
|
+
<span class="sr-only">Loading...</span>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
`
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { html } from 'htm/preact';
|
|
2
|
+
import { render } from 'preact';
|
|
3
|
+
import { useState, useEffect, useRef } from 'preact/hooks';
|
|
4
|
+
|
|
5
|
+
export function FileBrowserOpenBtn(props) {
|
|
6
|
+
function handleClick() {
|
|
7
|
+
|
|
8
|
+
}
|
|
9
|
+
return html`
|
|
10
|
+
<button onClick=${() => handleClick()} class='fileBrowserModalToggleBtn'>${props.buttonText}</button>
|
|
11
|
+
`
|
|
12
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { html } from 'htm/preact';
|
|
2
|
+
import { render } from 'preact';
|
|
3
|
+
import { useState, useEffect, useRef } from 'preact/hooks';
|
|
4
|
+
import { DirIcon } from './DirIcon.js'
|
|
5
|
+
import { FileIcon } from './FileIcon.js'
|
|
6
|
+
|
|
7
|
+
export function FileEntry(props) {
|
|
8
|
+
function handleClick(path) {
|
|
9
|
+
props.setChangedDir(path)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (props.file.isdir){
|
|
13
|
+
return html`
|
|
14
|
+
<div class="fileEntryContainer">
|
|
15
|
+
<input type="radio" name="dirchoice" value=${props.file.realpath} onClick=${() => props.setCurChosenDir(props.file.realpath)}></input>
|
|
16
|
+
<div class="fileLinkContainer" onClick=${() => handleClick(props.file.pathfromroot)}>
|
|
17
|
+
<${DirIcon}><//>
|
|
18
|
+
<li key=${props.key}>${props.file.basename}</li>
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
`
|
|
22
|
+
} else {
|
|
23
|
+
return html`
|
|
24
|
+
<div class="fileEntryContainer">
|
|
25
|
+
<div class="fileNonlinkContainer">
|
|
26
|
+
<${FileIcon}><//>
|
|
27
|
+
<li key=${props.key}>${props.file.basename}</li>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
`
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { html } from 'htm/preact';
|
|
2
|
+
import { render } from 'preact';
|
|
3
|
+
import { useState, useEffect, useRef } from 'preact/hooks';
|
|
4
|
+
|
|
5
|
+
export function FileIcon() {
|
|
6
|
+
return html`
|
|
7
|
+
<svg style="margin-right:8px;" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6" width="10px" height="10px">
|
|
8
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m2.25 0H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Z" />
|
|
9
|
+
</svg>
|
|
10
|
+
`
|
|
11
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { html } from 'htm/preact';
|
|
2
|
+
import { render } from 'preact';
|
|
3
|
+
import { useState, useEffect, useRef, useMemo } from 'preact/hooks';
|
|
4
|
+
import { FileBrowser } from './FileBrowser.js'
|
|
5
|
+
import { ArgparseFormEntry } from './ArgparseFormEntry.js'
|
|
6
|
+
|
|
7
|
+
export function OceanprocForm(props) {
|
|
8
|
+
const [displayRestOfForm, setDisplayRestOfForm] = useState(false)
|
|
9
|
+
const [argparseArgObjects, setArgparseArgObjects] = useState(null)
|
|
10
|
+
const [bidsPath, setBidsPath] = useState("")
|
|
11
|
+
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
const xhr = new XMLHttpRequest();
|
|
14
|
+
xhr.open('GET', '/api/get_parser_args/')
|
|
15
|
+
xhr.onload = () => {
|
|
16
|
+
if (xhr.status === 200) {
|
|
17
|
+
setArgparseArgObjects(JSON.parse(xhr.responseText))
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
xhr.send()
|
|
21
|
+
}, [])
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
if (bidsPath !== "") {
|
|
24
|
+
setDisplayRestOfForm(true)
|
|
25
|
+
} else {
|
|
26
|
+
setDisplayRestOfForm(false)
|
|
27
|
+
}
|
|
28
|
+
}, [bidsPath])
|
|
29
|
+
|
|
30
|
+
return html`
|
|
31
|
+
<div class="formContainer">
|
|
32
|
+
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#bidsDirForm">
|
|
33
|
+
Select your BIDS directory
|
|
34
|
+
</button>
|
|
35
|
+
<p>${bidsPath}</p>
|
|
36
|
+
<${FileBrowser}
|
|
37
|
+
modalId="bidsDirForm"
|
|
38
|
+
submitBtnText="Choose directory"
|
|
39
|
+
browserTitle="Choose a BIDS directory (click on a directory name to go into it, check the radio button to select it)"
|
|
40
|
+
handleSubmit=${(curChosenDir) => setBidsPath(curChosenDir)}><//>
|
|
41
|
+
<form>
|
|
42
|
+
${displayRestOfForm && argparseArgObjects !== null && html`
|
|
43
|
+
${argparseArgObjects.map(obj => html`
|
|
44
|
+
<${ArgparseFormEntry}
|
|
45
|
+
obj=${obj}
|
|
46
|
+
formName="restOfForm"
|
|
47
|
+
ignoredOptNames=${["bids_path"]}><//>
|
|
48
|
+
`)}
|
|
49
|
+
`}
|
|
50
|
+
</form>
|
|
51
|
+
</div>
|
|
52
|
+
`
|
|
53
|
+
}
|