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.
Files changed (41) hide show
  1. oceanproc/__init__.py +1 -0
  2. oceanproc/bids_wrapper.py +331 -0
  3. oceanproc/events_long.py +153 -0
  4. oceanproc/firstlevel/__init__.py +0 -0
  5. oceanproc/firstlevel/run_glm.py +923 -0
  6. oceanproc/firstlevel/tests/__init__.py +0 -0
  7. oceanproc/firstlevel/tests/test_run_glm.py +165 -0
  8. oceanproc/fmriprep_wrapper.py +222 -0
  9. oceanproc/group_series.py +228 -0
  10. oceanproc/grouplevel/__init__.py +0 -0
  11. oceanproc/grouplevel/computations.py +80 -0
  12. oceanproc/grouplevel/design_mat_utils.py +54 -0
  13. oceanproc/grouplevel/dscalar_utils.py +51 -0
  14. oceanproc/grouplevel/run.py +18 -0
  15. oceanproc/grouplevel/utils.py +4 -0
  16. oceanproc/ocean_proc.py +215 -0
  17. oceanproc/oceanparse.py +107 -0
  18. oceanproc/utils.py +224 -0
  19. oceanproc/web/TODO.md +4 -0
  20. oceanproc/web/__init__.py +0 -0
  21. oceanproc/web/app.py +78 -0
  22. oceanproc/web/config.json +7 -0
  23. oceanproc/web/get_parser_json.py +58 -0
  24. oceanproc/web/static/components/ArgparseForm.js +9 -0
  25. oceanproc/web/static/components/ArgparseFormEntry.js +52 -0
  26. oceanproc/web/static/components/DirIcon.js +11 -0
  27. oceanproc/web/static/components/FileBrowser.js +85 -0
  28. oceanproc/web/static/components/FileBrowserOpenBtn.js +12 -0
  29. oceanproc/web/static/components/FileEntry.js +32 -0
  30. oceanproc/web/static/components/FileIcon.js +11 -0
  31. oceanproc/web/static/components/OceanprocForm.js +53 -0
  32. oceanproc/web/static/components/styles.css +44 -0
  33. oceanproc/web/templates/filesystem.html +13 -0
  34. oceanproc/web/templates/filesystem_fail.html +2 -0
  35. oceanproc/web/templates/oceanproc_form.html +23 -0
  36. oceanproc/web/templates/react-test.html +0 -0
  37. oceanproc/web/templates/username_card_component_test.html +39 -0
  38. oceanproc-0.1.0.dist-info/METADATA +29 -0
  39. oceanproc-0.1.0.dist-info/RECORD +41 -0
  40. oceanproc-0.1.0.dist-info/WHEEL +4 -0
  41. 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,7 @@
1
+ {
2
+ "options_script_path": "../ocean_proc.py",
3
+ "parser_group_names": [
4
+ "session_arguments",
5
+ "config_arguments"
6
+ ]
7
+ }
@@ -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
+ }