ChessAnalysisPipeline 0.0.4__py3-none-any.whl → 0.0.6__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.
Potentially problematic release.
This version of ChessAnalysisPipeline might be problematic. Click here for more details.
- CHAP/TaskManager.py +214 -0
- CHAP/common/models/__init__.py +0 -2
- CHAP/common/models/integration.py +392 -249
- CHAP/common/models/map.py +350 -198
- CHAP/common/processor.py +229 -191
- CHAP/common/reader.py +52 -39
- CHAP/common/utils/__init__.py +0 -37
- CHAP/common/utils/fit.py +1197 -991
- CHAP/common/utils/general.py +629 -372
- CHAP/common/utils/material.py +158 -121
- CHAP/common/utils/scanparsers.py +735 -339
- CHAP/common/writer.py +31 -25
- CHAP/edd/models.py +65 -51
- CHAP/edd/processor.py +136 -113
- CHAP/edd/reader.py +1 -1
- CHAP/edd/writer.py +1 -1
- CHAP/inference/processor.py +35 -28
- CHAP/inference/reader.py +1 -1
- CHAP/inference/writer.py +1 -1
- CHAP/pipeline.py +14 -28
- CHAP/processor.py +44 -75
- CHAP/reader.py +49 -40
- CHAP/runner.py +73 -32
- CHAP/saxswaxs/processor.py +1 -1
- CHAP/saxswaxs/reader.py +1 -1
- CHAP/saxswaxs/writer.py +1 -1
- CHAP/server.py +130 -0
- CHAP/sin2psi/processor.py +1 -1
- CHAP/sin2psi/reader.py +1 -1
- CHAP/sin2psi/writer.py +1 -1
- CHAP/tomo/__init__.py +1 -4
- CHAP/tomo/models.py +53 -31
- CHAP/tomo/processor.py +1326 -902
- CHAP/tomo/reader.py +4 -2
- CHAP/tomo/writer.py +4 -2
- CHAP/writer.py +47 -41
- {ChessAnalysisPipeline-0.0.4.dist-info → ChessAnalysisPipeline-0.0.6.dist-info}/METADATA +1 -1
- ChessAnalysisPipeline-0.0.6.dist-info/RECORD +52 -0
- ChessAnalysisPipeline-0.0.4.dist-info/RECORD +0 -50
- {ChessAnalysisPipeline-0.0.4.dist-info → ChessAnalysisPipeline-0.0.6.dist-info}/LICENSE +0 -0
- {ChessAnalysisPipeline-0.0.4.dist-info → ChessAnalysisPipeline-0.0.6.dist-info}/WHEEL +0 -0
- {ChessAnalysisPipeline-0.0.4.dist-info → ChessAnalysisPipeline-0.0.6.dist-info}/entry_points.txt +0 -0
- {ChessAnalysisPipeline-0.0.4.dist-info → ChessAnalysisPipeline-0.0.6.dist-info}/top_level.txt +0 -0
CHAP/common/utils/general.py
CHANGED
|
@@ -1,45 +1,64 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
|
|
3
|
-
#FIX write a function that returns a list of peak indices for a given plot
|
|
4
|
-
#FIX use raise_error concept on more functions to optionally raise an error
|
|
5
|
-
|
|
6
2
|
# -*- coding: utf-8 -*-
|
|
3
|
+
#pylint: disable=
|
|
7
4
|
"""
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
File : general.py
|
|
6
|
+
Author : Rolf Verberg <rolfverberg AT gmail dot com>
|
|
7
|
+
Description: A collection of general modules
|
|
11
8
|
"""
|
|
9
|
+
# RV write function that returns a list of peak indices for a given plot
|
|
10
|
+
# RV use raise_error concept on more functions
|
|
12
11
|
|
|
13
|
-
|
|
14
|
-
logger = getLogger(__name__)
|
|
15
|
-
|
|
12
|
+
# System modules
|
|
16
13
|
from ast import literal_eval
|
|
14
|
+
from logging import getLogger
|
|
15
|
+
from os import path as os_path
|
|
16
|
+
from os import (
|
|
17
|
+
access,
|
|
18
|
+
R_OK,
|
|
19
|
+
)
|
|
17
20
|
from re import compile as re_compile
|
|
18
21
|
from re import split as re_split
|
|
19
22
|
from re import sub as re_sub
|
|
20
23
|
from sys import float_info
|
|
21
24
|
|
|
25
|
+
# Third party modules
|
|
22
26
|
import numpy as np
|
|
23
27
|
try:
|
|
24
28
|
import matplotlib.pyplot as plt
|
|
25
29
|
from matplotlib.widgets import Button
|
|
26
|
-
except:
|
|
30
|
+
except ImportError:
|
|
27
31
|
pass
|
|
28
32
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
return
|
|
33
|
+
logger = getLogger(__name__)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def depth_list(_list):
|
|
37
|
+
"""Return the depth of a list."""
|
|
38
|
+
return isinstance(_list, list) and 1+max(map(depth_list, _list))
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def depth_tuple(_tuple):
|
|
42
|
+
"""Return the depth of a tuple."""
|
|
43
|
+
return isinstance(_tuple, tuple) and 1+max(map(depth_tuple, _tuple))
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def unwrap_tuple(_tuple):
|
|
47
|
+
"""Unwrap a tuple."""
|
|
48
|
+
if depth_tuple(_tuple) > 1 and len(_tuple) == 1:
|
|
49
|
+
_tuple = unwrap_tuple(*_tuple)
|
|
50
|
+
return _tuple
|
|
51
|
+
|
|
35
52
|
|
|
36
53
|
def illegal_value(value, name, location=None, raise_error=False, log=True):
|
|
54
|
+
"""Print illegal value message and/or raise error."""
|
|
37
55
|
if not isinstance(location, str):
|
|
38
56
|
location = ''
|
|
39
57
|
else:
|
|
40
58
|
location = f'in {location} '
|
|
41
59
|
if isinstance(name, str):
|
|
42
|
-
error_msg =
|
|
60
|
+
error_msg = \
|
|
61
|
+
f'Illegal value for {name} {location}({value}, {type(value)})'
|
|
43
62
|
else:
|
|
44
63
|
error_msg = f'Illegal value {location}({value}, {type(value)})'
|
|
45
64
|
if log:
|
|
@@ -47,27 +66,36 @@ def illegal_value(value, name, location=None, raise_error=False, log=True):
|
|
|
47
66
|
if raise_error:
|
|
48
67
|
raise ValueError(error_msg)
|
|
49
68
|
|
|
50
|
-
|
|
69
|
+
|
|
70
|
+
def illegal_combination(
|
|
71
|
+
value1, name1, value2, name2, location=None, raise_error=False,
|
|
51
72
|
log=True):
|
|
73
|
+
"""Print illegal combination message and/or raise error."""
|
|
52
74
|
if not isinstance(location, str):
|
|
53
75
|
location = ''
|
|
54
76
|
else:
|
|
55
77
|
location = f'in {location} '
|
|
56
78
|
if isinstance(name1, str):
|
|
57
|
-
error_msg = f'Illegal combination for {name1} and {name2} {location}'
|
|
58
|
-
|
|
79
|
+
error_msg = f'Illegal combination for {name1} and {name2} {location}' \
|
|
80
|
+
f'({value1}, {type(value1)} and {value2}, {type(value2)})'
|
|
59
81
|
else:
|
|
60
|
-
error_msg = f'Illegal combination {location}'
|
|
61
|
-
|
|
82
|
+
error_msg = f'Illegal combination {location}' \
|
|
83
|
+
f'({value1}, {type(value1)} and {value2}, {type(value2)})'
|
|
62
84
|
if log:
|
|
63
85
|
logger.error(error_msg)
|
|
64
86
|
if raise_error:
|
|
65
87
|
raise ValueError(error_msg)
|
|
66
88
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
89
|
+
|
|
90
|
+
def test_ge_gt_le_lt(
|
|
91
|
+
ge, gt, le, lt, func, location=None, raise_error=False, log=True):
|
|
92
|
+
"""
|
|
93
|
+
Check individual and mutual validity of ge, gt, le, lt qualifiers.
|
|
94
|
+
|
|
95
|
+
:param func: Test for integers or numbers
|
|
96
|
+
:type func: callable: is_int, is_num
|
|
97
|
+
:return: True upon success or False when mutually exlusive
|
|
98
|
+
:rtype: bool
|
|
71
99
|
"""
|
|
72
100
|
if ge is None and gt is None and le is None and lt is None:
|
|
73
101
|
return True
|
|
@@ -95,21 +123,24 @@ def test_ge_gt_le_lt(ge, gt, le, lt, func, location=None, raise_error=False, log
|
|
|
95
123
|
if le is not None and ge > le:
|
|
96
124
|
illegal_combination(ge, 'ge', le, 'le', location, raise_error, log)
|
|
97
125
|
return False
|
|
98
|
-
|
|
126
|
+
if lt is not None and ge >= lt:
|
|
99
127
|
illegal_combination(ge, 'ge', lt, 'lt', location, raise_error, log)
|
|
100
128
|
return False
|
|
101
129
|
elif gt is not None:
|
|
102
130
|
if le is not None and gt >= le:
|
|
103
131
|
illegal_combination(gt, 'gt', le, 'le', location, raise_error, log)
|
|
104
132
|
return False
|
|
105
|
-
|
|
133
|
+
if lt is not None and gt >= lt:
|
|
106
134
|
illegal_combination(gt, 'gt', lt, 'lt', location, raise_error, log)
|
|
107
135
|
return False
|
|
108
136
|
return True
|
|
109
137
|
|
|
138
|
+
|
|
110
139
|
def range_string_ge_gt_le_lt(ge=None, gt=None, le=None, lt=None):
|
|
111
|
-
"""
|
|
112
|
-
|
|
140
|
+
"""
|
|
141
|
+
Return a range string representation matching the ge, gt, le, lt
|
|
142
|
+
qualifiers. Does not validate the inputs, do that as needed before
|
|
143
|
+
calling.
|
|
113
144
|
"""
|
|
114
145
|
range_string = ''
|
|
115
146
|
if ge is not None:
|
|
@@ -134,31 +165,45 @@ def range_string_ge_gt_le_lt(ge=None, gt=None, le=None, lt=None):
|
|
|
134
165
|
range_string += f'{lt})'
|
|
135
166
|
return range_string
|
|
136
167
|
|
|
168
|
+
|
|
137
169
|
def is_int(v, ge=None, gt=None, le=None, lt=None, raise_error=False, log=True):
|
|
138
|
-
"""
|
|
139
|
-
|
|
170
|
+
"""
|
|
171
|
+
Value is an integer in range ge <= v <= le or gt < v < lt or some
|
|
172
|
+
combination.
|
|
173
|
+
|
|
174
|
+
:return: True if yes or False is no
|
|
175
|
+
:rtype: bool
|
|
140
176
|
"""
|
|
141
177
|
return _is_int_or_num(v, 'int', ge, gt, le, lt, raise_error, log)
|
|
142
178
|
|
|
179
|
+
|
|
143
180
|
def is_num(v, ge=None, gt=None, le=None, lt=None, raise_error=False, log=True):
|
|
144
|
-
"""
|
|
145
|
-
|
|
181
|
+
"""
|
|
182
|
+
Value is a number in range ge <= v <= le or gt < v < lt or some
|
|
183
|
+
combination.
|
|
184
|
+
|
|
185
|
+
:return: True if yes or False is no
|
|
186
|
+
:rtype: bool
|
|
146
187
|
"""
|
|
147
188
|
return _is_int_or_num(v, 'num', ge, gt, le, lt, raise_error, log)
|
|
148
189
|
|
|
149
|
-
|
|
190
|
+
|
|
191
|
+
def _is_int_or_num(
|
|
192
|
+
v, type_str, ge=None, gt=None, le=None, lt=None, raise_error=False,
|
|
150
193
|
log=True):
|
|
151
194
|
if type_str == 'int':
|
|
152
195
|
if not isinstance(v, int):
|
|
153
196
|
illegal_value(v, 'v', '_is_int_or_num', raise_error, log)
|
|
154
197
|
return False
|
|
155
|
-
if not test_ge_gt_le_lt(
|
|
198
|
+
if not test_ge_gt_le_lt(
|
|
199
|
+
ge, gt, le, lt, is_int, '_is_int_or_num', raise_error, log):
|
|
156
200
|
return False
|
|
157
201
|
elif type_str == 'num':
|
|
158
202
|
if not isinstance(v, (int, float)):
|
|
159
203
|
illegal_value(v, 'v', '_is_int_or_num', raise_error, log)
|
|
160
204
|
return False
|
|
161
|
-
if not test_ge_gt_le_lt(
|
|
205
|
+
if not test_ge_gt_le_lt(
|
|
206
|
+
ge, gt, le, lt, is_num, '_is_int_or_num', raise_error, log):
|
|
162
207
|
return False
|
|
163
208
|
else:
|
|
164
209
|
illegal_value(type_str, 'type_str', '_is_int_or_num', raise_error, log)
|
|
@@ -186,145 +231,192 @@ def _is_int_or_num(v, type_str, ge=None, gt=None, le=None, lt=None, raise_error=
|
|
|
186
231
|
return False
|
|
187
232
|
return True
|
|
188
233
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
234
|
+
|
|
235
|
+
def is_int_pair(
|
|
236
|
+
v, ge=None, gt=None, le=None, lt=None, raise_error=False, log=True):
|
|
237
|
+
"""
|
|
238
|
+
Value is an integer pair, each in range ge <= v[i] <= le or
|
|
239
|
+
gt < v[i] < lt or ge[i] <= v[i] <= le[i] or gt[i] < v[i] < lt[i]
|
|
240
|
+
or some combination.
|
|
241
|
+
|
|
242
|
+
:return: True if yes or False is no
|
|
243
|
+
:rtype: bool
|
|
193
244
|
"""
|
|
194
245
|
return _is_int_or_num_pair(v, 'int', ge, gt, le, lt, raise_error, log)
|
|
195
246
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
247
|
+
|
|
248
|
+
def is_num_pair(
|
|
249
|
+
v, ge=None, gt=None, le=None, lt=None, raise_error=False, log=True):
|
|
250
|
+
"""
|
|
251
|
+
Value is a number pair, each in range ge <= v[i] <= le or
|
|
252
|
+
gt < v[i] < lt or ge[i] <= v[i] <= le[i] or gt[i] < v[i] < lt[i]
|
|
253
|
+
or some combination.
|
|
254
|
+
|
|
255
|
+
:return: True if yes or False is no
|
|
256
|
+
:rtype: bool
|
|
200
257
|
"""
|
|
201
258
|
return _is_int_or_num_pair(v, 'num', ge, gt, le, lt, raise_error, log)
|
|
202
259
|
|
|
203
|
-
|
|
260
|
+
|
|
261
|
+
def _is_int_or_num_pair(
|
|
262
|
+
v, type_str, ge=None, gt=None, le=None, lt=None, raise_error=False,
|
|
204
263
|
log=True):
|
|
205
264
|
if type_str == 'int':
|
|
206
|
-
if not (isinstance(v, (tuple, list)) and len(v) == 2
|
|
207
|
-
isinstance(v[1], int)):
|
|
265
|
+
if not (isinstance(v, (tuple, list)) and len(v) == 2
|
|
266
|
+
and isinstance(v[0], int) and isinstance(v[1], int)):
|
|
208
267
|
illegal_value(v, 'v', '_is_int_or_num_pair', raise_error, log)
|
|
209
268
|
return False
|
|
210
269
|
func = is_int
|
|
211
270
|
elif type_str == 'num':
|
|
212
|
-
if not (isinstance(v, (tuple, list)) and len(v) == 2
|
|
213
|
-
isinstance(v[
|
|
271
|
+
if not (isinstance(v, (tuple, list)) and len(v) == 2
|
|
272
|
+
and isinstance(v[0], (int, float))
|
|
273
|
+
and isinstance(v[1], (int, float))):
|
|
214
274
|
illegal_value(v, 'v', '_is_int_or_num_pair', raise_error, log)
|
|
215
275
|
return False
|
|
216
276
|
func = is_num
|
|
217
277
|
else:
|
|
218
|
-
illegal_value(
|
|
278
|
+
illegal_value(
|
|
279
|
+
type_str, 'type_str', '_is_int_or_num_pair', raise_error, log)
|
|
219
280
|
return False
|
|
220
281
|
if ge is None and gt is None and le is None and lt is None:
|
|
221
282
|
return True
|
|
222
283
|
if ge is None or func(ge, log=True):
|
|
223
284
|
ge = 2*[ge]
|
|
224
|
-
elif not _is_int_or_num_pair(
|
|
285
|
+
elif not _is_int_or_num_pair(
|
|
286
|
+
ge, type_str, raise_error=raise_error, log=log):
|
|
225
287
|
return False
|
|
226
288
|
if gt is None or func(gt, log=True):
|
|
227
289
|
gt = 2*[gt]
|
|
228
|
-
elif not _is_int_or_num_pair(
|
|
290
|
+
elif not _is_int_or_num_pair(
|
|
291
|
+
gt, type_str, raise_error=raise_error, log=log):
|
|
229
292
|
return False
|
|
230
293
|
if le is None or func(le, log=True):
|
|
231
294
|
le = 2*[le]
|
|
232
|
-
elif not _is_int_or_num_pair(
|
|
295
|
+
elif not _is_int_or_num_pair(
|
|
296
|
+
le, type_str, raise_error=raise_error, log=log):
|
|
233
297
|
return False
|
|
234
298
|
if lt is None or func(lt, log=True):
|
|
235
299
|
lt = 2*[lt]
|
|
236
|
-
elif not _is_int_or_num_pair(
|
|
300
|
+
elif not _is_int_or_num_pair(
|
|
301
|
+
lt, type_str, raise_error=raise_error, log=log):
|
|
237
302
|
return False
|
|
238
|
-
if (not func(v[0], ge[0], gt[0], le[0], lt[0], raise_error, log)
|
|
239
|
-
not func(v[1], ge[1], gt[1], le[1], lt[1], raise_error, log)):
|
|
303
|
+
if (not func(v[0], ge[0], gt[0], le[0], lt[0], raise_error, log)
|
|
304
|
+
or not func(v[1], ge[1], gt[1], le[1], lt[1], raise_error, log)):
|
|
240
305
|
return False
|
|
241
306
|
return True
|
|
242
307
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
308
|
+
|
|
309
|
+
def is_int_series(
|
|
310
|
+
t_or_l, ge=None, gt=None, le=None, lt=None, raise_error=False,
|
|
311
|
+
log=True):
|
|
312
|
+
"""
|
|
313
|
+
Value is a tuple or list of integers, each in range
|
|
314
|
+
ge <= l[i] <= le or gt < l[i] < lt or some combination.
|
|
246
315
|
"""
|
|
247
|
-
if not test_ge_gt_le_lt(
|
|
316
|
+
if not test_ge_gt_le_lt(
|
|
317
|
+
ge, gt, le, lt, is_int, 'is_int_series', raise_error, log):
|
|
248
318
|
return False
|
|
249
|
-
if not isinstance(
|
|
250
|
-
illegal_value(
|
|
319
|
+
if not isinstance(t_or_l, (tuple, list)):
|
|
320
|
+
illegal_value(t_or_l, 't_or_l', 'is_int_series', raise_error, log)
|
|
251
321
|
return False
|
|
252
|
-
if any(
|
|
322
|
+
if any(not is_int(v, ge, gt, le, lt, raise_error, log) for v in t_or_l):
|
|
253
323
|
return False
|
|
254
324
|
return True
|
|
255
325
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
326
|
+
|
|
327
|
+
def is_num_series(
|
|
328
|
+
t_or_l, ge=None, gt=None, le=None, lt=None, raise_error=False,
|
|
329
|
+
log=True):
|
|
259
330
|
"""
|
|
260
|
-
|
|
331
|
+
Value is a tuple or list of numbers, each in range ge <= l[i] <= le
|
|
332
|
+
or gt < l[i] < lt or some combination.
|
|
333
|
+
"""
|
|
334
|
+
if not test_ge_gt_le_lt(
|
|
335
|
+
ge, gt, le, lt, is_int, 'is_int_series', raise_error, log):
|
|
261
336
|
return False
|
|
262
|
-
if not isinstance(
|
|
263
|
-
illegal_value(
|
|
337
|
+
if not isinstance(t_or_l, (tuple, list)):
|
|
338
|
+
illegal_value(t_or_l, 't_or_l', 'is_num_series', raise_error, log)
|
|
264
339
|
return False
|
|
265
|
-
if any(
|
|
340
|
+
if any(not is_num(v, ge, gt, le, lt, raise_error, log) for v in t_or_l):
|
|
266
341
|
return False
|
|
267
342
|
return True
|
|
268
343
|
|
|
269
|
-
|
|
270
|
-
|
|
344
|
+
|
|
345
|
+
def is_str_series(t_or_l, raise_error=False, log=True):
|
|
271
346
|
"""
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
347
|
+
Value is a tuple or list of strings.
|
|
348
|
+
"""
|
|
349
|
+
if (not isinstance(t_or_l, (tuple, list))
|
|
350
|
+
or any(not isinstance(s, str) for s in t_or_l)):
|
|
351
|
+
illegal_value(t_or_l, 't_or_l', 'is_str_series', raise_error, log)
|
|
275
352
|
return False
|
|
276
353
|
return True
|
|
277
354
|
|
|
278
|
-
|
|
279
|
-
|
|
355
|
+
|
|
356
|
+
def is_dict_series(t_or_l, raise_error=False, log=True):
|
|
280
357
|
"""
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
358
|
+
Value is a tuple or list of dictionaries.
|
|
359
|
+
"""
|
|
360
|
+
if (not isinstance(t_or_l, (tuple, list))
|
|
361
|
+
or any(not isinstance(d, dict) for d in t_or_l)):
|
|
362
|
+
illegal_value(t_or_l, 't_or_l', 'is_dict_series', raise_error, log)
|
|
284
363
|
return False
|
|
285
364
|
return True
|
|
286
365
|
|
|
287
|
-
|
|
288
|
-
|
|
366
|
+
|
|
367
|
+
def is_dict_nums(d, raise_error=False, log=True):
|
|
289
368
|
"""
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
369
|
+
Value is a dictionary with single number values
|
|
370
|
+
"""
|
|
371
|
+
if (not isinstance(d, dict)
|
|
372
|
+
or any(not is_num(v, log=False) for v in d.values())):
|
|
373
|
+
illegal_value(d, 'd', 'is_dict_nums', raise_error, log)
|
|
293
374
|
return False
|
|
294
375
|
return True
|
|
295
376
|
|
|
296
|
-
|
|
297
|
-
|
|
377
|
+
|
|
378
|
+
def is_dict_strings(d, raise_error=False, log=True):
|
|
298
379
|
"""
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
380
|
+
Value is a dictionary with single string values
|
|
381
|
+
"""
|
|
382
|
+
if (not isinstance(d, dict)
|
|
383
|
+
or any(not isinstance(v, str) for v in d.values())):
|
|
384
|
+
illegal_value(d, 'd', 'is_dict_strings', raise_error, log)
|
|
302
385
|
return False
|
|
303
386
|
return True
|
|
304
387
|
|
|
388
|
+
|
|
305
389
|
def is_index(v, ge=0, lt=None, raise_error=False, log=True):
|
|
306
|
-
"""
|
|
307
|
-
|
|
390
|
+
"""
|
|
391
|
+
Value is an array index in range ge <= v < lt. NOTE lt IS NOT
|
|
392
|
+
included!
|
|
308
393
|
"""
|
|
309
394
|
if isinstance(lt, int):
|
|
310
395
|
if lt <= ge:
|
|
311
|
-
illegal_combination(
|
|
396
|
+
illegal_combination(
|
|
397
|
+
ge, 'ge', lt, 'lt', 'is_index', raise_error, log)
|
|
312
398
|
return False
|
|
313
399
|
return is_int(v, ge=ge, lt=lt, raise_error=raise_error, log=log)
|
|
314
400
|
|
|
401
|
+
|
|
315
402
|
def is_index_range(v, ge=0, le=None, lt=None, raise_error=False, log=True):
|
|
316
|
-
"""
|
|
317
|
-
|
|
403
|
+
"""
|
|
404
|
+
Value is an array index range in range ge <= v[0] <= v[1] <= le or
|
|
405
|
+
ge <= v[0] <= v[1] < lt. NOTE le IS included!
|
|
318
406
|
"""
|
|
319
407
|
if not is_int_pair(v, raise_error=raise_error, log=log):
|
|
320
408
|
return False
|
|
321
|
-
if not test_ge_gt_le_lt(
|
|
409
|
+
if not test_ge_gt_le_lt(
|
|
410
|
+
ge, None, le, lt, is_int, 'is_index_range', raise_error, log):
|
|
322
411
|
return False
|
|
323
|
-
if not ge <= v[0] <= v[1] or (le is not None and v[1] > le)
|
|
412
|
+
if (not ge <= v[0] <= v[1] or (le is not None and v[1] > le)
|
|
413
|
+
or (lt is not None and v[1] >= lt)):
|
|
324
414
|
if le is not None:
|
|
325
|
-
error_msg =
|
|
415
|
+
error_msg = \
|
|
416
|
+
f'Value {v} out of range: !({ge} <= {v[0]} <= {v[1]} <= {le})'
|
|
326
417
|
else:
|
|
327
|
-
error_msg =
|
|
418
|
+
error_msg = \
|
|
419
|
+
f'Value {v} out of range: !({ge} <= {v[0]} <= {v[1]} < {lt})'
|
|
328
420
|
if log:
|
|
329
421
|
logger.error(error_msg)
|
|
330
422
|
if raise_error:
|
|
@@ -332,149 +424,193 @@ def is_index_range(v, ge=0, le=None, lt=None, raise_error=False, log=True):
|
|
|
332
424
|
return False
|
|
333
425
|
return True
|
|
334
426
|
|
|
427
|
+
|
|
335
428
|
def index_nearest(a, value):
|
|
429
|
+
"""Return index of nearest array value."""
|
|
336
430
|
a = np.asarray(a)
|
|
337
431
|
if a.ndim > 1:
|
|
338
|
-
raise ValueError(
|
|
432
|
+
raise ValueError(
|
|
433
|
+
f'Invalid array dimension for parameter a ({a.ndim}, {a})')
|
|
339
434
|
# Round up for .5
|
|
340
435
|
value *= 1.0+float_info.epsilon
|
|
341
436
|
return (int)(np.argmin(np.abs(a-value)))
|
|
342
437
|
|
|
438
|
+
|
|
343
439
|
def index_nearest_low(a, value):
|
|
440
|
+
"""Return index of nearest array value, rounded down"""
|
|
344
441
|
a = np.asarray(a)
|
|
345
442
|
if a.ndim > 1:
|
|
346
|
-
raise ValueError(
|
|
443
|
+
raise ValueError(
|
|
444
|
+
f'Invalid array dimension for parameter a ({a.ndim}, {a})')
|
|
347
445
|
index = int(np.argmin(np.abs(a-value)))
|
|
348
446
|
if value < a[index] and index > 0:
|
|
349
447
|
index -= 1
|
|
350
448
|
return index
|
|
351
449
|
|
|
450
|
+
|
|
352
451
|
def index_nearest_upp(a, value):
|
|
452
|
+
"""Return index of nearest array value, rounded upp."""
|
|
353
453
|
a = np.asarray(a)
|
|
354
454
|
if a.ndim > 1:
|
|
355
|
-
raise ValueError(
|
|
455
|
+
raise ValueError(
|
|
456
|
+
f'Invalid array dimension for parameter a ({a.ndim}, {a})')
|
|
356
457
|
index = int(np.argmin(np.abs(a-value)))
|
|
357
458
|
if value > a[index] and index < a.size-1:
|
|
358
459
|
index += 1
|
|
359
460
|
return index
|
|
360
461
|
|
|
462
|
+
|
|
361
463
|
def round_to_n(x, n=1):
|
|
464
|
+
"""Round to a specific number of decimals."""
|
|
362
465
|
if x == 0.0:
|
|
363
466
|
return 0
|
|
364
|
-
|
|
365
|
-
|
|
467
|
+
return type(x)(round(x, n-1-int(np.floor(np.log10(abs(x))))))
|
|
468
|
+
|
|
366
469
|
|
|
367
470
|
def round_up_to_n(x, n=1):
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
471
|
+
"""Round up to a specific number of decimals."""
|
|
472
|
+
x_round = round_to_n(x, n)
|
|
473
|
+
if abs(x/x_round) > 1.0:
|
|
474
|
+
x_round += np.sign(x) * 10**(np.floor(np.log10(abs(x)))+1-n)
|
|
475
|
+
return type(x)(x_round)
|
|
476
|
+
|
|
372
477
|
|
|
373
478
|
def trunc_to_n(x, n=1):
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
479
|
+
"""Truncate to a specific number of decimals."""
|
|
480
|
+
x_round = round_to_n(x, n)
|
|
481
|
+
if abs(x_round/x) > 1.0:
|
|
482
|
+
x_round -= np.sign(x) * 10**(np.floor(np.log10(abs(x)))+1-n)
|
|
483
|
+
return type(x)(x_round)
|
|
484
|
+
|
|
378
485
|
|
|
379
486
|
def almost_equal(a, b, sig_figs):
|
|
487
|
+
"""
|
|
488
|
+
Check if equal to within a certain number of significant digits.
|
|
489
|
+
"""
|
|
380
490
|
if is_num(a) and is_num(b):
|
|
381
|
-
return abs(round_to_n(a-b, sig_figs)) < pow(10, -sig_figs
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
491
|
+
return abs(round_to_n(a-b, sig_figs)) < pow(10, 1-sig_figs)
|
|
492
|
+
raise ValueError(
|
|
493
|
+
f'Invalid value for a or b in almost_equal (a: {a}, {type(a)}, '
|
|
494
|
+
f'b: {b}, {type(b)})')
|
|
495
|
+
|
|
386
496
|
|
|
387
497
|
def string_to_list(s, split_on_dash=True, remove_duplicates=True, sort=True):
|
|
388
|
-
"""
|
|
389
|
-
|
|
390
|
-
|
|
498
|
+
"""
|
|
499
|
+
Return a list of numbers by splitting/expanding a string on any
|
|
500
|
+
combination of commas, whitespaces, or dashes (when
|
|
501
|
+
split_on_dash=True).
|
|
502
|
+
e.g: '1, 3, 5-8, 12 ' -> [1, 3, 5, 6, 7, 8, 12]
|
|
391
503
|
"""
|
|
392
504
|
if not isinstance(s, str):
|
|
393
|
-
illegal_value(s, location='string_to_list')
|
|
505
|
+
illegal_value(s, 's', location='string_to_list')
|
|
394
506
|
return None
|
|
395
|
-
if not
|
|
507
|
+
if not s:
|
|
396
508
|
return []
|
|
397
509
|
try:
|
|
398
|
-
|
|
510
|
+
list1 = re_split(r'\s+,\s+|\s+,|,\s+|\s+|,', s.strip())
|
|
399
511
|
except (ValueError, TypeError, SyntaxError, MemoryError, RecursionError):
|
|
400
512
|
return None
|
|
401
513
|
if split_on_dash:
|
|
402
514
|
try:
|
|
403
|
-
|
|
404
|
-
for
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
515
|
+
l_of_i = []
|
|
516
|
+
for v in list1:
|
|
517
|
+
list2 = [
|
|
518
|
+
literal_eval(x)
|
|
519
|
+
for x in re_split(r'\s+-\s+|\s+-|-\s+|\s+|-', v)]
|
|
520
|
+
if len(list2) == 1:
|
|
521
|
+
l_of_i += list2
|
|
522
|
+
elif len(list2) == 2 and list2[1] > list2[0]:
|
|
523
|
+
l_of_i += list(range(list2[0], 1+list2[1]))
|
|
410
524
|
else:
|
|
411
525
|
raise ValueError
|
|
412
|
-
except (ValueError, TypeError, SyntaxError, MemoryError,
|
|
526
|
+
except (ValueError, TypeError, SyntaxError, MemoryError,
|
|
527
|
+
RecursionError):
|
|
413
528
|
return None
|
|
414
529
|
else:
|
|
415
|
-
|
|
530
|
+
l_of_i = [literal_eval(x) for x in list1]
|
|
416
531
|
if remove_duplicates:
|
|
417
|
-
|
|
532
|
+
l_of_i = list(dict.fromkeys(l_of_i))
|
|
418
533
|
if sort:
|
|
419
|
-
|
|
420
|
-
return
|
|
534
|
+
l_of_i = sorted(l_of_i)
|
|
535
|
+
return l_of_i
|
|
536
|
+
|
|
421
537
|
|
|
422
538
|
def get_trailing_int(string):
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
539
|
+
"""Get the trailing integer in a string."""
|
|
540
|
+
index_regex = re_compile(r'\d+$')
|
|
541
|
+
match = index_regex.search(string)
|
|
542
|
+
if match is None:
|
|
426
543
|
return None
|
|
427
|
-
|
|
428
|
-
return int(mo.group())
|
|
544
|
+
return int(match.group())
|
|
429
545
|
|
|
430
|
-
|
|
546
|
+
|
|
547
|
+
def input_int(
|
|
548
|
+
s=None, ge=None, gt=None, le=None, lt=None, default=None, inset=None,
|
|
431
549
|
raise_error=False, log=True):
|
|
432
|
-
|
|
550
|
+
"""Interactively prompt the user to enter an integer."""
|
|
551
|
+
return _input_int_or_num(
|
|
552
|
+
'int', s, ge, gt, le, lt, default, inset, raise_error, log)
|
|
433
553
|
|
|
434
|
-
def input_num(s=None, ge=None, gt=None, le=None, lt=None, default=None, raise_error=False,
|
|
435
|
-
log=True):
|
|
436
|
-
return _input_int_or_num('num', s, ge, gt, le, lt, default, None, raise_error,log)
|
|
437
554
|
|
|
438
|
-
def
|
|
555
|
+
def input_num(
|
|
556
|
+
s=None, ge=None, gt=None, le=None, lt=None, default=None,
|
|
557
|
+
raise_error=False, log=True):
|
|
558
|
+
"""Interactively prompt the user to enter a number."""
|
|
559
|
+
return _input_int_or_num(
|
|
560
|
+
'num', s, ge, gt, le, lt, default, None, raise_error,log)
|
|
561
|
+
|
|
562
|
+
|
|
563
|
+
def _input_int_or_num(
|
|
564
|
+
type_str, s=None, ge=None, gt=None, le=None, lt=None, default=None,
|
|
439
565
|
inset=None, raise_error=False, log=True):
|
|
566
|
+
"""Interactively prompt the user to enter an integer or number."""
|
|
440
567
|
if type_str == 'int':
|
|
441
|
-
if not test_ge_gt_le_lt(
|
|
568
|
+
if not test_ge_gt_le_lt(
|
|
569
|
+
ge, gt, le, lt, is_int, '_input_int_or_num', raise_error, log):
|
|
442
570
|
return None
|
|
443
571
|
elif type_str == 'num':
|
|
444
|
-
if not test_ge_gt_le_lt(
|
|
572
|
+
if not test_ge_gt_le_lt(
|
|
573
|
+
ge, gt, le, lt, is_num, '_input_int_or_num', raise_error, log):
|
|
445
574
|
return None
|
|
446
575
|
else:
|
|
447
|
-
illegal_value(
|
|
576
|
+
illegal_value(
|
|
577
|
+
type_str, 'type_str', '_input_int_or_num', raise_error, log)
|
|
448
578
|
return None
|
|
449
579
|
if default is not None:
|
|
450
|
-
if not _is_int_or_num(
|
|
580
|
+
if not _is_int_or_num(
|
|
581
|
+
default, type_str, raise_error=raise_error, log=log):
|
|
451
582
|
return None
|
|
452
583
|
if ge is not None and default < ge:
|
|
453
|
-
illegal_combination(
|
|
584
|
+
illegal_combination(
|
|
585
|
+
ge, 'ge', default, 'default', '_input_int_or_num', raise_error,
|
|
454
586
|
log)
|
|
455
587
|
return None
|
|
456
588
|
if gt is not None and default <= gt:
|
|
457
|
-
illegal_combination(
|
|
589
|
+
illegal_combination(
|
|
590
|
+
gt, 'gt', default, 'default', '_input_int_or_num', raise_error,
|
|
458
591
|
log)
|
|
459
592
|
return None
|
|
460
593
|
if le is not None and default > le:
|
|
461
|
-
illegal_combination(
|
|
594
|
+
illegal_combination(
|
|
595
|
+
le, 'le', default, 'default', '_input_int_or_num', raise_error,
|
|
462
596
|
log)
|
|
463
597
|
return None
|
|
464
598
|
if lt is not None and default >= lt:
|
|
465
|
-
illegal_combination(
|
|
599
|
+
illegal_combination(
|
|
600
|
+
lt, 'lt', default, 'default', '_input_int_or_num', raise_error,
|
|
466
601
|
log)
|
|
467
602
|
return None
|
|
468
603
|
default_string = f' [{default}]'
|
|
469
604
|
else:
|
|
470
605
|
default_string = ''
|
|
471
606
|
if inset is not None:
|
|
472
|
-
if (not isinstance(inset, (tuple, list))
|
|
473
|
-
|
|
474
|
-
illegal_value(
|
|
607
|
+
if (not isinstance(inset, (tuple, list))
|
|
608
|
+
or any(not isinstance(i, int) for i in inset)):
|
|
609
|
+
illegal_value(
|
|
610
|
+
inset, 'inset', '_input_int_or_num', raise_error, log)
|
|
475
611
|
return None
|
|
476
612
|
v_range = f'{range_string_ge_gt_le_lt(ge, gt, le, lt)}'
|
|
477
|
-
if
|
|
613
|
+
if v_range:
|
|
478
614
|
v_range = f' {v_range}'
|
|
479
615
|
if s is None:
|
|
480
616
|
if type_str == 'int':
|
|
@@ -485,89 +621,104 @@ def _input_int_or_num(type_str, s=None, ge=None, gt=None, le=None, lt=None, defa
|
|
|
485
621
|
print(f'{s}{v_range}{default_string}: ')
|
|
486
622
|
try:
|
|
487
623
|
i = input()
|
|
488
|
-
if isinstance(i, str) and not
|
|
624
|
+
if isinstance(i, str) and not i:
|
|
489
625
|
v = default
|
|
490
626
|
print(f'{v}')
|
|
491
627
|
else:
|
|
492
628
|
v = literal_eval(i)
|
|
493
629
|
if inset and v not in inset:
|
|
494
|
-
|
|
630
|
+
raise ValueError(f'{v} not part of the set {inset}')
|
|
495
631
|
except (ValueError, TypeError, SyntaxError, MemoryError, RecursionError):
|
|
496
632
|
v = None
|
|
497
|
-
except:
|
|
498
|
-
if log:
|
|
499
|
-
logger.error('Unexpected error')
|
|
500
|
-
if raise_error:
|
|
501
|
-
raise ValueError('Unexpected error')
|
|
502
633
|
if not _is_int_or_num(v, type_str, ge, gt, le, lt):
|
|
503
|
-
v = _input_int_or_num(
|
|
634
|
+
v = _input_int_or_num(
|
|
635
|
+
type_str, s, ge, gt, le, lt, default, inset, raise_error, log)
|
|
504
636
|
return v
|
|
505
637
|
|
|
506
|
-
|
|
638
|
+
|
|
639
|
+
def input_int_list(
|
|
640
|
+
s=None, ge=None, le=None, split_on_dash=True, remove_duplicates=True,
|
|
507
641
|
sort=True, raise_error=False, log=True):
|
|
508
|
-
"""Prompt the user to input a list of interger and split the entered string on any combination
|
|
509
|
-
of commas, whitespaces, or dashes (when split_on_dash is True)
|
|
510
|
-
e.g: '1 3,5-8 , 12 ' -> [1, 3, 5, 6, 7, 8, 12]
|
|
511
|
-
remove_duplicates: removes duplicates if True (may also change the order)
|
|
512
|
-
sort: sort in ascending order if True
|
|
513
|
-
return None upon an illegal input
|
|
514
642
|
"""
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
sort: sort in ascending order if True
|
|
525
|
-
return None upon an illegal input
|
|
643
|
+
Prompt the user to input a list of interger and split the entered
|
|
644
|
+
string on any combination of commas, whitespaces, or dashes (when
|
|
645
|
+
split_on_dash is True).
|
|
646
|
+
e.g: '1 3,5-8 , 12 ' -> [1, 3, 5, 6, 7, 8, 12]
|
|
647
|
+
|
|
648
|
+
remove_duplicates: removes duplicates if True (may also change the
|
|
649
|
+
order)
|
|
650
|
+
sort: sort in ascending order if True
|
|
651
|
+
return None upon an illegal input
|
|
526
652
|
"""
|
|
527
|
-
return _input_int_or_num_list(
|
|
653
|
+
return _input_int_or_num_list(
|
|
654
|
+
'int', s, ge, le, split_on_dash, remove_duplicates, sort, raise_error,
|
|
528
655
|
log)
|
|
529
656
|
|
|
530
|
-
|
|
657
|
+
|
|
658
|
+
def input_num_list(
|
|
659
|
+
s=None, ge=None, le=None, remove_duplicates=True, sort=True,
|
|
660
|
+
raise_error=False, log=True):
|
|
661
|
+
"""
|
|
662
|
+
Prompt the user to input a list of numbers and split the entered
|
|
663
|
+
string on any combination of commas or whitespaces.
|
|
664
|
+
e.g: '1.0, 3, 5.8, 12 ' -> [1.0, 3.0, 5.8, 12.0]
|
|
665
|
+
|
|
666
|
+
remove_duplicates: removes duplicates if True (may also change the
|
|
667
|
+
order)
|
|
668
|
+
sort: sort in ascending order if True
|
|
669
|
+
return None upon an illegal input
|
|
670
|
+
"""
|
|
671
|
+
return _input_int_or_num_list(
|
|
672
|
+
'num', s, ge, le, False, remove_duplicates, sort, raise_error, log)
|
|
673
|
+
|
|
674
|
+
|
|
675
|
+
def _input_int_or_num_list(
|
|
676
|
+
type_str, s=None, ge=None, le=None, split_on_dash=True,
|
|
531
677
|
remove_duplicates=True, sort=True, raise_error=False, log=True):
|
|
532
|
-
#
|
|
678
|
+
# RV do we want a limit on max dimension?
|
|
533
679
|
if type_str == 'int':
|
|
534
|
-
if not test_ge_gt_le_lt(
|
|
535
|
-
|
|
680
|
+
if not test_ge_gt_le_lt(
|
|
681
|
+
ge, None, le, None, is_int, 'input_int_or_num_list',
|
|
682
|
+
raise_error, log):
|
|
536
683
|
return None
|
|
537
684
|
elif type_str == 'num':
|
|
538
|
-
if not test_ge_gt_le_lt(
|
|
539
|
-
|
|
685
|
+
if not test_ge_gt_le_lt(
|
|
686
|
+
ge, None, le, None, is_num, 'input_int_or_num_list',
|
|
687
|
+
raise_error, log):
|
|
540
688
|
return None
|
|
541
689
|
else:
|
|
542
690
|
illegal_value(type_str, 'type_str', '_input_int_or_num_list')
|
|
543
691
|
return None
|
|
544
692
|
v_range = f'{range_string_ge_gt_le_lt(ge=ge, le=le)}'
|
|
545
|
-
if
|
|
693
|
+
if v_range:
|
|
546
694
|
v_range = f' (each value in {v_range})'
|
|
547
695
|
if s is None:
|
|
548
696
|
print(f'Enter a series of integers{v_range}: ')
|
|
549
697
|
else:
|
|
550
698
|
print(f'{s}{v_range}: ')
|
|
551
699
|
try:
|
|
552
|
-
|
|
700
|
+
_list = string_to_list(input(), split_on_dash, remove_duplicates, sort)
|
|
553
701
|
except (ValueError, TypeError, SyntaxError, MemoryError, RecursionError):
|
|
554
|
-
|
|
702
|
+
_list = None
|
|
555
703
|
except:
|
|
556
704
|
print('Unexpected error')
|
|
557
705
|
raise
|
|
558
|
-
if (not isinstance(
|
|
559
|
-
|
|
706
|
+
if (not isinstance(_list, list) or any(
|
|
707
|
+
not _is_int_or_num(v, type_str, ge=ge, le=le) for v in _list)):
|
|
560
708
|
if split_on_dash:
|
|
561
|
-
print('Invalid input: enter a valid set of dash/comma/whitespace
|
|
562
|
-
|
|
709
|
+
print('Invalid input: enter a valid set of dash/comma/whitespace '
|
|
710
|
+
'separated integers e.g. 1 3,5-8 , 12')
|
|
563
711
|
else:
|
|
564
|
-
print('Invalid input: enter a valid set of comma/whitespace
|
|
565
|
-
|
|
566
|
-
|
|
712
|
+
print('Invalid input: enter a valid set of comma/whitespace '
|
|
713
|
+
'separated integers e.g. 1 3,5 8 , 12')
|
|
714
|
+
_list = _input_int_or_num_list(
|
|
715
|
+
type_str, s, ge, le, split_on_dash, remove_duplicates, sort,
|
|
567
716
|
raise_error, log)
|
|
568
|
-
return
|
|
717
|
+
return _list
|
|
718
|
+
|
|
569
719
|
|
|
570
720
|
def input_yesno(s=None, default=None):
|
|
721
|
+
"""Interactively prompt the user to enter a y/n question."""
|
|
571
722
|
if default is not None:
|
|
572
723
|
if not isinstance(default, str):
|
|
573
724
|
illegal_value(default, 'default', 'input_yesno')
|
|
@@ -587,7 +738,7 @@ def input_yesno(s=None, default=None):
|
|
|
587
738
|
else:
|
|
588
739
|
print(f'{s}{default_string}: ')
|
|
589
740
|
i = input()
|
|
590
|
-
if isinstance(i, str) and not
|
|
741
|
+
if isinstance(i, str) and not i:
|
|
591
742
|
i = default
|
|
592
743
|
print(f'{i}')
|
|
593
744
|
if i is not None and i.lower() in 'yes':
|
|
@@ -599,29 +750,33 @@ def input_yesno(s=None, default=None):
|
|
|
599
750
|
v = input_yesno(s, default)
|
|
600
751
|
return v
|
|
601
752
|
|
|
753
|
+
|
|
602
754
|
def input_menu(items, default=None, header=None):
|
|
603
|
-
|
|
604
|
-
|
|
755
|
+
"""Interactively prompt the user to select from a menu."""
|
|
756
|
+
if (not isinstance(items, (tuple, list))
|
|
757
|
+
or any(not isinstance(i, str) for i in items)):
|
|
605
758
|
illegal_value(items, 'items', 'input_menu')
|
|
606
759
|
return None
|
|
607
760
|
if default is not None:
|
|
608
761
|
if not (isinstance(default, str) and default in items):
|
|
609
|
-
logger.error(
|
|
762
|
+
logger.error(
|
|
763
|
+
f'Invalid value for default ({default}), must be in {items}')
|
|
610
764
|
return None
|
|
611
|
-
default_string = f' [{items.index(default)
|
|
765
|
+
default_string = f' [{1+items.index(default)}]'
|
|
612
766
|
else:
|
|
613
767
|
default_string = ''
|
|
614
768
|
if header is None:
|
|
615
|
-
print(
|
|
769
|
+
print('Choose one of the following items '
|
|
770
|
+
f'(1, {len(items)}){default_string}:')
|
|
616
771
|
else:
|
|
617
772
|
print(f'{header} (1, {len(items)}){default_string}:')
|
|
618
773
|
for i, choice in enumerate(items):
|
|
619
774
|
print(f' {i+1}: {choice}')
|
|
620
775
|
try:
|
|
621
|
-
choice
|
|
622
|
-
if isinstance(choice, str) and not
|
|
776
|
+
choice = input()
|
|
777
|
+
if isinstance(choice, str) and not choice:
|
|
623
778
|
choice = items.index(default)
|
|
624
|
-
print(f'{choice
|
|
779
|
+
print(f'{1+choice}')
|
|
625
780
|
else:
|
|
626
781
|
choice = literal_eval(choice)
|
|
627
782
|
if isinstance(choice, int) and 1 <= choice <= len(items):
|
|
@@ -638,141 +793,185 @@ def input_menu(items, default=None, header=None):
|
|
|
638
793
|
choice = input_menu(items, default)
|
|
639
794
|
return choice
|
|
640
795
|
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
796
|
+
|
|
797
|
+
def assert_no_duplicates_in_list_of_dicts(_list, raise_error=False):
|
|
798
|
+
"""
|
|
799
|
+
Assert that there are no duplicates in a list of dictionaries.
|
|
800
|
+
"""
|
|
801
|
+
if not isinstance(_list, list):
|
|
802
|
+
illegal_value(
|
|
803
|
+
_list, '_list', 'assert_no_duplicates_in_list_of_dicts',
|
|
804
|
+
raise_error)
|
|
644
805
|
return None
|
|
645
|
-
if any(
|
|
646
|
-
illegal_value(
|
|
806
|
+
if any(not isinstance(d, dict) for d in _list):
|
|
807
|
+
illegal_value(
|
|
808
|
+
_list, '_list', 'assert_no_duplicates_in_list_of_dicts',
|
|
809
|
+
raise_error)
|
|
647
810
|
return None
|
|
648
|
-
if len(
|
|
811
|
+
if (len(_list) != len([dict(_tuple) for _tuple in
|
|
812
|
+
{tuple(sorted(d.items())) for d in _list}])):
|
|
649
813
|
if raise_error:
|
|
650
|
-
raise ValueError(f'Duplicate items found in {
|
|
651
|
-
|
|
652
|
-
logger.error(f'Duplicate items found in {l}')
|
|
814
|
+
raise ValueError(f'Duplicate items found in {_list}')
|
|
815
|
+
logger.error(f'Duplicate items found in {_list}')
|
|
653
816
|
return None
|
|
654
|
-
|
|
655
|
-
|
|
817
|
+
return _list
|
|
818
|
+
|
|
656
819
|
|
|
657
|
-
def assert_no_duplicate_key_in_list_of_dicts(
|
|
820
|
+
def assert_no_duplicate_key_in_list_of_dicts(_list, key, raise_error=False):
|
|
821
|
+
"""
|
|
822
|
+
Assert that there are no duplicate keys in a list of dictionaries.
|
|
823
|
+
"""
|
|
658
824
|
if not isinstance(key, str):
|
|
659
|
-
illegal_value(
|
|
825
|
+
illegal_value(
|
|
826
|
+
key, 'key', 'assert_no_duplicate_key_in_list_of_dicts',
|
|
827
|
+
raise_error)
|
|
660
828
|
return None
|
|
661
|
-
if not isinstance(
|
|
662
|
-
illegal_value(
|
|
829
|
+
if not isinstance(_list, list):
|
|
830
|
+
illegal_value(
|
|
831
|
+
_list, '_list', 'assert_no_duplicate_key_in_list_of_dicts',
|
|
832
|
+
raise_error)
|
|
663
833
|
return None
|
|
664
|
-
if any(
|
|
665
|
-
illegal_value(
|
|
834
|
+
if any(isinstance(d, dict) for d in _list):
|
|
835
|
+
illegal_value(
|
|
836
|
+
_list, '_list', 'assert_no_duplicates_in_list_of_dicts',
|
|
837
|
+
raise_error)
|
|
666
838
|
return None
|
|
667
|
-
keys = [d.get(key, None) for d in
|
|
668
|
-
if None in keys or len(set(keys)) != len(
|
|
839
|
+
keys = [d.get(key, None) for d in _list]
|
|
840
|
+
if None in keys or len(set(keys)) != len(_list):
|
|
669
841
|
if raise_error:
|
|
670
|
-
raise ValueError(
|
|
671
|
-
|
|
672
|
-
|
|
842
|
+
raise ValueError(
|
|
843
|
+
f'Duplicate or missing key ({key}) found in {_list}')
|
|
844
|
+
logger.error(f'Duplicate or missing key ({key}) found in {_list}')
|
|
673
845
|
return None
|
|
674
|
-
|
|
675
|
-
return l
|
|
846
|
+
return _list
|
|
676
847
|
|
|
677
|
-
|
|
848
|
+
|
|
849
|
+
def assert_no_duplicate_attr_in_list_of_objs(_list, attr, raise_error=False):
|
|
850
|
+
"""
|
|
851
|
+
Assert that there are no duplicate attributes in a list of objects.
|
|
852
|
+
"""
|
|
678
853
|
if not isinstance(attr, str):
|
|
679
|
-
illegal_value(
|
|
854
|
+
illegal_value(
|
|
855
|
+
attr, 'attr', 'assert_no_duplicate_attr_in_list_of_objs',
|
|
856
|
+
raise_error)
|
|
680
857
|
return None
|
|
681
|
-
if not isinstance(
|
|
682
|
-
illegal_value(
|
|
858
|
+
if not isinstance(_list, list):
|
|
859
|
+
illegal_value(
|
|
860
|
+
_list, '_list', 'assert_no_duplicate_key_in_list_of_objs',
|
|
861
|
+
raise_error)
|
|
683
862
|
return None
|
|
684
|
-
attrs = [getattr(obj, attr, None) for obj in
|
|
685
|
-
if None in attrs or len(set(attrs)) != len(
|
|
863
|
+
attrs = [getattr(obj, attr, None) for obj in _list]
|
|
864
|
+
if None in attrs or len(set(attrs)) != len(_list):
|
|
686
865
|
if raise_error:
|
|
687
|
-
raise ValueError(
|
|
688
|
-
|
|
689
|
-
|
|
866
|
+
raise ValueError(
|
|
867
|
+
f'Duplicate or missing attr ({attr}) found in {_list}')
|
|
868
|
+
logger.error(f'Duplicate or missing attr ({attr}) found in {_list}')
|
|
690
869
|
return None
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
def file_exists_and_readable(
|
|
695
|
-
|
|
696
|
-
if not
|
|
697
|
-
raise ValueError(f'{
|
|
698
|
-
|
|
699
|
-
raise ValueError(f'{
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
def draw_mask_1d(
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
870
|
+
return _list
|
|
871
|
+
|
|
872
|
+
|
|
873
|
+
def file_exists_and_readable(f):
|
|
874
|
+
"""Check if a file exists and is readable."""
|
|
875
|
+
if not os_path.isfile(f):
|
|
876
|
+
raise ValueError(f'{f} is not a valid file')
|
|
877
|
+
if not access(f, R_OK):
|
|
878
|
+
raise ValueError(f'{f} is not accessible for reading')
|
|
879
|
+
return f
|
|
880
|
+
|
|
881
|
+
|
|
882
|
+
def draw_mask_1d(
|
|
883
|
+
ydata, xdata=None, current_index_ranges=None, current_mask=None,
|
|
884
|
+
select_mask=True, num_index_ranges_max=None, title=None, legend=None,
|
|
885
|
+
test_mode=False):
|
|
886
|
+
"""Display a 2D plot and have the user select a mask."""
|
|
887
|
+
# RV make color blind friendly
|
|
888
|
+
def draw_selections(
|
|
889
|
+
ax, current_include, current_exclude, selected_index_ranges):
|
|
890
|
+
"""Draw the selections."""
|
|
707
891
|
ax.clear()
|
|
708
892
|
ax.set_title(title)
|
|
709
893
|
ax.legend([legend])
|
|
710
894
|
ax.plot(xdata, ydata, 'k')
|
|
711
|
-
for
|
|
712
|
-
xlow = 0.5*(xdata[max(0, low-1)]+xdata[low])
|
|
713
|
-
xupp = 0.5*(xdata[upp]+xdata[min(num_data-1, upp
|
|
895
|
+
for low, upp in current_include:
|
|
896
|
+
xlow = 0.5 * (xdata[max(0, low-1)]+xdata[low])
|
|
897
|
+
xupp = 0.5 * (xdata[upp]+xdata[min(num_data-1, 1+upp)])
|
|
714
898
|
ax.axvspan(xlow, xupp, facecolor='green', alpha=0.5)
|
|
715
|
-
for
|
|
716
|
-
xlow = 0.5*(xdata[max(0, low-1)]+xdata[low])
|
|
717
|
-
xupp = 0.5*(xdata[upp]+xdata[min(num_data-1, upp
|
|
899
|
+
for low, upp in current_exclude:
|
|
900
|
+
xlow = 0.5 * (xdata[max(0, low-1)]+xdata[low])
|
|
901
|
+
xupp = 0.5 * (xdata[upp]+xdata[min(num_data-1, 1+upp)])
|
|
718
902
|
ax.axvspan(xlow, xupp, facecolor='red', alpha=0.5)
|
|
719
|
-
for
|
|
720
|
-
xlow = 0.5*(xdata[max(0, low-1)]+xdata[low])
|
|
721
|
-
xupp = 0.5*(xdata[upp]+xdata[min(num_data-1, upp
|
|
903
|
+
for low, upp in selected_index_ranges:
|
|
904
|
+
xlow = 0.5 * (xdata[max(0, low-1)]+xdata[low])
|
|
905
|
+
xupp = 0.5 * (xdata[upp]+xdata[min(num_data-1, 1+upp)])
|
|
722
906
|
ax.axvspan(xlow, xupp, facecolor=selection_color, alpha=0.5)
|
|
723
907
|
ax.get_figure().canvas.draw()
|
|
724
908
|
|
|
725
909
|
def onclick(event):
|
|
910
|
+
"""Action taken on clicking the mouse button."""
|
|
726
911
|
if event.inaxes in [fig.axes[0]]:
|
|
727
912
|
selected_index_ranges.append(index_nearest_upp(xdata, event.xdata))
|
|
728
913
|
|
|
729
914
|
def onrelease(event):
|
|
730
|
-
|
|
915
|
+
"""Action taken on releasing the mouse button."""
|
|
916
|
+
if selected_index_ranges:
|
|
731
917
|
if isinstance(selected_index_ranges[-1], int):
|
|
732
918
|
if event.inaxes in [fig.axes[0]]:
|
|
733
919
|
event.xdata = index_nearest_low(xdata, event.xdata)
|
|
734
920
|
if selected_index_ranges[-1] <= event.xdata:
|
|
735
|
-
selected_index_ranges[-1] =
|
|
921
|
+
selected_index_ranges[-1] = \
|
|
922
|
+
(selected_index_ranges[-1], event.xdata)
|
|
736
923
|
else:
|
|
737
|
-
selected_index_ranges[-1] =
|
|
738
|
-
|
|
924
|
+
selected_index_ranges[-1] = \
|
|
925
|
+
(event.xdata, selected_index_ranges[-1])
|
|
926
|
+
draw_selections(
|
|
927
|
+
event.inaxes, current_include, current_exclude,
|
|
928
|
+
selected_index_ranges)
|
|
739
929
|
else:
|
|
740
930
|
selected_index_ranges.pop(-1)
|
|
741
931
|
|
|
742
932
|
def confirm_selection(event):
|
|
933
|
+
"""Action taken on hitting the confirm button."""
|
|
743
934
|
plt.close()
|
|
744
935
|
|
|
745
936
|
def clear_last_selection(event):
|
|
746
|
-
|
|
937
|
+
"""Action taken on hitting the clear button."""
|
|
938
|
+
if selected_index_ranges:
|
|
747
939
|
selected_index_ranges.pop(-1)
|
|
748
940
|
else:
|
|
749
|
-
while
|
|
941
|
+
while current_include:
|
|
750
942
|
current_include.pop()
|
|
751
|
-
while
|
|
943
|
+
while current_exclude:
|
|
752
944
|
current_exclude.pop()
|
|
753
945
|
selected_mask.fill(False)
|
|
754
|
-
draw_selections(
|
|
946
|
+
draw_selections(
|
|
947
|
+
ax, current_include, current_exclude, selected_index_ranges)
|
|
755
948
|
|
|
756
949
|
def update_mask(mask, selected_index_ranges, unselected_index_ranges):
|
|
757
|
-
|
|
758
|
-
|
|
950
|
+
"""Update the plot with the selected mask."""
|
|
951
|
+
for low, upp in selected_index_ranges:
|
|
952
|
+
selected_mask = np.logical_and(
|
|
953
|
+
xdata >= xdata[low], xdata <= xdata[upp])
|
|
759
954
|
mask = np.logical_or(mask, selected_mask)
|
|
760
|
-
for
|
|
761
|
-
unselected_mask = np.logical_and(
|
|
955
|
+
for low, upp in unselected_index_ranges:
|
|
956
|
+
unselected_mask = np.logical_and(
|
|
957
|
+
xdata >= xdata[low], xdata <= xdata[upp])
|
|
762
958
|
mask[unselected_mask] = False
|
|
763
959
|
return mask
|
|
764
960
|
|
|
765
961
|
def update_index_ranges(mask):
|
|
766
|
-
|
|
962
|
+
"""
|
|
963
|
+
Update the currently included index ranges (where mask = True).
|
|
964
|
+
"""
|
|
767
965
|
current_include = []
|
|
768
966
|
for i, m in enumerate(mask):
|
|
769
|
-
if m
|
|
770
|
-
if
|
|
967
|
+
if m:
|
|
968
|
+
if (not current_include
|
|
969
|
+
or isinstance(current_include[-1], tuple)):
|
|
771
970
|
current_include.append(i)
|
|
772
971
|
else:
|
|
773
|
-
if
|
|
972
|
+
if current_include and isinstance(current_include[-1], int):
|
|
774
973
|
current_include[-1] = (current_include[-1], i-1)
|
|
775
|
-
if
|
|
974
|
+
if current_include and isinstance(current_include[-1], int):
|
|
776
975
|
current_include[-1] = (current_include[-1], num_data-1)
|
|
777
976
|
return current_include
|
|
778
977
|
|
|
@@ -794,21 +993,25 @@ def draw_mask_1d(ydata, xdata=None, current_index_ranges=None, current_mask=None
|
|
|
794
993
|
return None, None
|
|
795
994
|
if current_index_ranges is not None:
|
|
796
995
|
if not isinstance(current_index_ranges, (tuple, list)):
|
|
797
|
-
logger.warning(
|
|
798
|
-
|
|
996
|
+
logger.warning(
|
|
997
|
+
'Invalid current_index_ranges parameter '
|
|
998
|
+
f'({current_index_ranges}, {type(current_index_ranges)})')
|
|
799
999
|
return None, None
|
|
800
1000
|
if not isinstance(select_mask, bool):
|
|
801
|
-
logger.warning(
|
|
1001
|
+
logger.warning(
|
|
1002
|
+
f'Invalid select_mask parameter ({select_mask}, '
|
|
1003
|
+
f'{type(select_mask)})')
|
|
802
1004
|
return None, None
|
|
803
1005
|
if num_index_ranges_max is not None:
|
|
804
|
-
logger.warning(
|
|
1006
|
+
logger.warning(
|
|
1007
|
+
'num_index_ranges_max input not yet implemented in draw_mask_1d')
|
|
805
1008
|
if title is None:
|
|
806
1009
|
title = 'select ranges of data'
|
|
807
1010
|
elif not isinstance(title, str):
|
|
808
|
-
|
|
1011
|
+
illegal_value(title, 'title')
|
|
809
1012
|
title = ''
|
|
810
1013
|
if legend is None and not isinstance(title, str):
|
|
811
|
-
|
|
1014
|
+
illegal_value(legend, 'legend')
|
|
812
1015
|
legend = None
|
|
813
1016
|
|
|
814
1017
|
if select_mask:
|
|
@@ -818,7 +1021,8 @@ def draw_mask_1d(ydata, xdata=None, current_index_ranges=None, current_mask=None
|
|
|
818
1021
|
title = f'Click and drag to {title} you wish to exclude'
|
|
819
1022
|
selection_color = 'red'
|
|
820
1023
|
|
|
821
|
-
# Set initial selected mask and the selected/unselected index
|
|
1024
|
+
# Set initial selected mask and the selected/unselected index
|
|
1025
|
+
# ranges as needed
|
|
822
1026
|
selected_index_ranges = []
|
|
823
1027
|
unselected_index_ranges = []
|
|
824
1028
|
selected_mask = np.full(xdata.shape, False, dtype=bool)
|
|
@@ -829,17 +1033,16 @@ def draw_mask_1d(ydata, xdata=None, current_index_ranges=None, current_mask=None
|
|
|
829
1033
|
selected_mask = np.full(xdata.shape, True, dtype=bool)
|
|
830
1034
|
else:
|
|
831
1035
|
selected_mask = np.copy(np.asarray(current_mask, dtype=bool))
|
|
832
|
-
if current_index_ranges is not None and
|
|
833
|
-
current_index_ranges = sorted(
|
|
834
|
-
for
|
|
1036
|
+
if current_index_ranges is not None and current_index_ranges:
|
|
1037
|
+
current_index_ranges = sorted(list(current_index_ranges))
|
|
1038
|
+
for low, upp in current_index_ranges:
|
|
835
1039
|
if low > upp or low >= num_data or upp < 0:
|
|
836
1040
|
continue
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
if upp >= num_data:
|
|
840
|
-
upp = num_data-1
|
|
1041
|
+
low = max(low, 0)
|
|
1042
|
+
upp = min(upp, num_data-1)
|
|
841
1043
|
selected_index_ranges.append((low, upp))
|
|
842
|
-
selected_mask = update_mask(
|
|
1044
|
+
selected_mask = update_mask(
|
|
1045
|
+
selected_mask, selected_index_ranges, unselected_index_ranges)
|
|
843
1046
|
if current_index_ranges is not None and current_mask is not None:
|
|
844
1047
|
selected_mask = np.logical_and(current_mask, selected_mask)
|
|
845
1048
|
if current_mask is not None:
|
|
@@ -849,7 +1052,7 @@ def draw_mask_1d(ydata, xdata=None, current_index_ranges=None, current_mask=None
|
|
|
849
1052
|
current_include = selected_index_ranges
|
|
850
1053
|
current_exclude = []
|
|
851
1054
|
selected_index_ranges = []
|
|
852
|
-
if not
|
|
1055
|
+
if not current_include:
|
|
853
1056
|
if select_mask:
|
|
854
1057
|
current_exclude = [(0, num_data-1)]
|
|
855
1058
|
else:
|
|
@@ -858,9 +1061,10 @@ def draw_mask_1d(ydata, xdata=None, current_index_ranges=None, current_mask=None
|
|
|
858
1061
|
if current_include[0][0] > 0:
|
|
859
1062
|
current_exclude.append((0, current_include[0][0]-1))
|
|
860
1063
|
for i in range(1, len(current_include)):
|
|
861
|
-
current_exclude.append(
|
|
1064
|
+
current_exclude.append(
|
|
1065
|
+
(1+current_include[i-1][1], current_include[i][0]-1))
|
|
862
1066
|
if current_include[-1][1] < num_data-1:
|
|
863
|
-
current_exclude.append((current_include[-1][1]
|
|
1067
|
+
current_exclude.append((1+current_include[-1][1], num_data-1))
|
|
864
1068
|
|
|
865
1069
|
if not test_mode:
|
|
866
1070
|
|
|
@@ -868,7 +1072,8 @@ def draw_mask_1d(ydata, xdata=None, current_index_ranges=None, current_mask=None
|
|
|
868
1072
|
plt.close('all')
|
|
869
1073
|
fig, ax = plt.subplots()
|
|
870
1074
|
plt.subplots_adjust(bottom=0.2)
|
|
871
|
-
draw_selections(
|
|
1075
|
+
draw_selections(
|
|
1076
|
+
ax, current_include, current_exclude, selected_index_ranges)
|
|
872
1077
|
|
|
873
1078
|
# Set up event handling for click-and-drag range selection
|
|
874
1079
|
cid_click = fig.canvas.mpl_connect('button_press_event', onclick)
|
|
@@ -891,26 +1096,36 @@ def draw_mask_1d(ydata, xdata=None, current_index_ranges=None, current_mask=None
|
|
|
891
1096
|
|
|
892
1097
|
# Swap selection depending on select_mask
|
|
893
1098
|
if not select_mask:
|
|
894
|
-
selected_index_ranges, unselected_index_ranges =
|
|
895
|
-
|
|
1099
|
+
selected_index_ranges, unselected_index_ranges = \
|
|
1100
|
+
unselected_index_ranges, selected_index_ranges
|
|
896
1101
|
|
|
897
1102
|
# Update the mask with the currently selected/unselected x-ranges
|
|
898
|
-
selected_mask = update_mask(
|
|
1103
|
+
selected_mask = update_mask(
|
|
1104
|
+
selected_mask, selected_index_ranges, unselected_index_ranges)
|
|
899
1105
|
|
|
900
1106
|
# Update the currently included index ranges (where mask is True)
|
|
901
1107
|
current_include = update_index_ranges(selected_mask)
|
|
902
1108
|
|
|
903
|
-
|
|
1109
|
+
return selected_mask, current_include
|
|
1110
|
+
|
|
1111
|
+
|
|
1112
|
+
def select_image_bounds(
|
|
1113
|
+
a, axis, low=None, upp=None, num_min=None, title='select array bounds',
|
|
904
1114
|
raise_error=False):
|
|
905
|
-
"""
|
|
1115
|
+
"""
|
|
1116
|
+
Interactively select the lower and upper data bounds for a 2D
|
|
1117
|
+
numpy array.
|
|
906
1118
|
"""
|
|
907
1119
|
a = np.asarray(a)
|
|
908
1120
|
if a.ndim != 2:
|
|
909
|
-
illegal_value(
|
|
910
|
-
|
|
1121
|
+
illegal_value(
|
|
1122
|
+
a.ndim, 'array dimension', location='select_image_bounds',
|
|
1123
|
+
raise_error=raise_error)
|
|
911
1124
|
return None
|
|
912
1125
|
if axis < 0 or axis >= a.ndim:
|
|
913
|
-
illegal_value(
|
|
1126
|
+
illegal_value(
|
|
1127
|
+
axis, 'axis', location='select_image_bounds',
|
|
1128
|
+
raise_error=raise_error)
|
|
914
1129
|
return None
|
|
915
1130
|
low_save = low
|
|
916
1131
|
upp_save = upp
|
|
@@ -919,7 +1134,9 @@ def select_image_bounds(a, axis, low=None, upp=None, num_min=None, title='select
|
|
|
919
1134
|
num_min = 1
|
|
920
1135
|
else:
|
|
921
1136
|
if num_min < 2 or num_min > a.shape[axis]:
|
|
922
|
-
logger.warning(
|
|
1137
|
+
logger.warning(
|
|
1138
|
+
'Invalid input for num_min in select_image_bounds, '
|
|
1139
|
+
'input ignored')
|
|
923
1140
|
num_min = 1
|
|
924
1141
|
if low is None:
|
|
925
1142
|
min_ = 0
|
|
@@ -927,21 +1144,26 @@ def select_image_bounds(a, axis, low=None, upp=None, num_min=None, title='select
|
|
|
927
1144
|
low_max = a.shape[axis]-num_min
|
|
928
1145
|
while True:
|
|
929
1146
|
if axis:
|
|
930
|
-
quick_imshow(
|
|
931
|
-
|
|
1147
|
+
quick_imshow(
|
|
1148
|
+
a[:,min_:max_], title=title, aspect='auto',
|
|
1149
|
+
extent=[min_,max_,a.shape[0],0])
|
|
932
1150
|
else:
|
|
933
|
-
quick_imshow(
|
|
934
|
-
|
|
935
|
-
|
|
1151
|
+
quick_imshow(
|
|
1152
|
+
a[min_:max_,:], title=title, aspect='auto',
|
|
1153
|
+
extent=[0,a.shape[1], max_,min_])
|
|
1154
|
+
zoom_flag = input_yesno(
|
|
1155
|
+
'Set lower data bound (y) or zoom in (n)?', 'y')
|
|
936
1156
|
if zoom_flag:
|
|
937
1157
|
low = input_int(' Set lower data bound', ge=0, le=low_max)
|
|
938
1158
|
break
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
1159
|
+
min_ = input_int(' Set lower zoom index', ge=0, le=low_max)
|
|
1160
|
+
max_ = input_int(
|
|
1161
|
+
' Set upper zoom index', ge=min_+1, le=low_max+1)
|
|
942
1162
|
else:
|
|
943
1163
|
if not is_int(low, ge=0, le=a.shape[axis]-num_min):
|
|
944
|
-
illegal_value(
|
|
1164
|
+
illegal_value(
|
|
1165
|
+
low, 'low', location='select_image_bounds',
|
|
1166
|
+
raise_error=raise_error)
|
|
945
1167
|
return None
|
|
946
1168
|
if upp is None:
|
|
947
1169
|
min_ = low+num_min
|
|
@@ -949,21 +1171,28 @@ def select_image_bounds(a, axis, low=None, upp=None, num_min=None, title='select
|
|
|
949
1171
|
upp_min = min_
|
|
950
1172
|
while True:
|
|
951
1173
|
if axis:
|
|
952
|
-
quick_imshow(
|
|
953
|
-
|
|
1174
|
+
quick_imshow(
|
|
1175
|
+
a[:,min_:max_], title=title, aspect='auto',
|
|
1176
|
+
extent=[min_,max_,a.shape[0],0])
|
|
954
1177
|
else:
|
|
955
|
-
quick_imshow(
|
|
956
|
-
|
|
957
|
-
|
|
1178
|
+
quick_imshow(
|
|
1179
|
+
a[min_:max_,:], title=title, aspect='auto',
|
|
1180
|
+
extent=[0,a.shape[1], max_,min_])
|
|
1181
|
+
zoom_flag = input_yesno(
|
|
1182
|
+
'Set upper data bound (y) or zoom in (n)?', 'y')
|
|
958
1183
|
if zoom_flag:
|
|
959
|
-
upp = input_int(
|
|
1184
|
+
upp = input_int(
|
|
1185
|
+
' Set upper data bound', ge=upp_min, le=a.shape[axis])
|
|
960
1186
|
break
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
1187
|
+
min_ = input_int(
|
|
1188
|
+
' Set upper zoom index', ge=upp_min, le=a.shape[axis]-1)
|
|
1189
|
+
max_ = input_int(
|
|
1190
|
+
' Set upper zoom index', ge=min_+1, le=a.shape[axis])
|
|
964
1191
|
else:
|
|
965
1192
|
if not is_int(upp, ge=low+num_min, le=a.shape[axis]):
|
|
966
|
-
illegal_value(
|
|
1193
|
+
illegal_value(
|
|
1194
|
+
upp, 'upp', location='select_image_bounds',
|
|
1195
|
+
raise_error=raise_error)
|
|
967
1196
|
return None
|
|
968
1197
|
bounds = (low, upp)
|
|
969
1198
|
a_tmp = np.copy(a)
|
|
@@ -978,22 +1207,29 @@ def select_image_bounds(a, axis, low=None, upp=None, num_min=None, title='select
|
|
|
978
1207
|
quick_imshow(a_tmp, title=title, aspect='auto')
|
|
979
1208
|
del a_tmp
|
|
980
1209
|
if not input_yesno('Accept these bounds (y/n)?', 'y'):
|
|
981
|
-
bounds = select_image_bounds(
|
|
1210
|
+
bounds = select_image_bounds(
|
|
1211
|
+
a, axis, low=low_save, upp=upp_save, num_min=num_min_save,
|
|
982
1212
|
title=title)
|
|
983
1213
|
clear_imshow(title)
|
|
984
1214
|
return bounds
|
|
985
1215
|
|
|
986
|
-
|
|
1216
|
+
|
|
1217
|
+
def select_one_image_bound(
|
|
1218
|
+
a, axis, bound=None, bound_name=None, title='select array bounds',
|
|
987
1219
|
default='y', raise_error=False):
|
|
988
|
-
"""
|
|
1220
|
+
"""
|
|
1221
|
+
Interactively select a data boundary for a 2D numpy array.
|
|
989
1222
|
"""
|
|
990
1223
|
a = np.asarray(a)
|
|
991
1224
|
if a.ndim != 2:
|
|
992
|
-
illegal_value(
|
|
993
|
-
|
|
1225
|
+
illegal_value(
|
|
1226
|
+
a.ndim, 'array dimension', location='select_one_image_bound',
|
|
1227
|
+
raise_error=raise_error)
|
|
994
1228
|
return None
|
|
995
1229
|
if axis < 0 or axis >= a.ndim:
|
|
996
|
-
illegal_value(
|
|
1230
|
+
illegal_value(
|
|
1231
|
+
axis, 'axis', location='select_one_image_bound',
|
|
1232
|
+
raise_error=raise_error)
|
|
997
1233
|
return None
|
|
998
1234
|
if bound_name is None:
|
|
999
1235
|
bound_name = 'data bound'
|
|
@@ -1003,22 +1239,27 @@ def select_one_image_bound(a, axis, bound=None, bound_name=None, title='select a
|
|
|
1003
1239
|
bound_max = a.shape[axis]-1
|
|
1004
1240
|
while True:
|
|
1005
1241
|
if axis:
|
|
1006
|
-
quick_imshow(
|
|
1007
|
-
|
|
1242
|
+
quick_imshow(
|
|
1243
|
+
a[:,min_:max_], title=title, aspect='auto',
|
|
1244
|
+
extent=[min_,max_,a.shape[0],0])
|
|
1008
1245
|
else:
|
|
1009
|
-
quick_imshow(
|
|
1010
|
-
|
|
1011
|
-
|
|
1246
|
+
quick_imshow(
|
|
1247
|
+
a[min_:max_,:], title=title, aspect='auto',
|
|
1248
|
+
extent=[0,a.shape[1], max_,min_])
|
|
1249
|
+
zoom_flag = input_yesno(
|
|
1250
|
+
f'Set {bound_name} (y) or zoom in (n)?', 'y')
|
|
1012
1251
|
if zoom_flag:
|
|
1013
1252
|
bound = input_int(f' Set {bound_name}', ge=0, le=bound_max)
|
|
1014
1253
|
clear_imshow(title)
|
|
1015
1254
|
break
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1255
|
+
min_ = input_int(' Set lower zoom index', ge=0, le=bound_max)
|
|
1256
|
+
max_ = input_int(
|
|
1257
|
+
' Set upper zoom index', ge=min_+1, le=bound_max+1)
|
|
1019
1258
|
|
|
1020
1259
|
elif not is_int(bound, ge=0, le=a.shape[axis]-1):
|
|
1021
|
-
illegal_value(
|
|
1260
|
+
illegal_value(
|
|
1261
|
+
bound, 'bound', location='select_one_image_bound',
|
|
1262
|
+
raise_error=raise_error)
|
|
1022
1263
|
return None
|
|
1023
1264
|
else:
|
|
1024
1265
|
print(f'Current {bound_name} = {bound}')
|
|
@@ -1031,11 +1272,14 @@ def select_one_image_bound(a, axis, bound=None, bound_name=None, title='select a
|
|
|
1031
1272
|
quick_imshow(a_tmp, title=title, aspect='auto')
|
|
1032
1273
|
del a_tmp
|
|
1033
1274
|
if not input_yesno(f'Accept this {bound_name} (y/n)?', default):
|
|
1034
|
-
bound = select_one_image_bound(
|
|
1275
|
+
bound = select_one_image_bound(
|
|
1276
|
+
a, axis, bound_name=bound_name, title=title)
|
|
1035
1277
|
clear_imshow(title)
|
|
1036
1278
|
return bound
|
|
1037
1279
|
|
|
1280
|
+
|
|
1038
1281
|
def clear_imshow(title=None):
|
|
1282
|
+
"""Clear an image opened by quick_imshow()."""
|
|
1039
1283
|
plt.ioff()
|
|
1040
1284
|
if title is None:
|
|
1041
1285
|
title = 'quick imshow'
|
|
@@ -1043,7 +1287,9 @@ def clear_imshow(title=None):
|
|
|
1043
1287
|
raise ValueError(f'Invalid parameter title ({title})')
|
|
1044
1288
|
plt.close(fig=title)
|
|
1045
1289
|
|
|
1290
|
+
|
|
1046
1291
|
def clear_plot(title=None):
|
|
1292
|
+
"""Clear an image opened by quick_plot()."""
|
|
1047
1293
|
plt.ioff()
|
|
1048
1294
|
if title is None:
|
|
1049
1295
|
title = 'quick plot'
|
|
@@ -1051,9 +1297,12 @@ def clear_plot(title=None):
|
|
|
1051
1297
|
raise ValueError(f'Invalid parameter title ({title})')
|
|
1052
1298
|
plt.close(fig=title)
|
|
1053
1299
|
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1300
|
+
|
|
1301
|
+
def quick_imshow(
|
|
1302
|
+
a, title=None, path=None, name=None, save_fig=False, save_only=False,
|
|
1303
|
+
clear=True, extent=None, show_grid=False, grid_color='w',
|
|
1304
|
+
grid_linewidth=1, block=False, **kwargs):
|
|
1305
|
+
"""Display a 2D image."""
|
|
1057
1306
|
if title is not None and not isinstance(title, str):
|
|
1058
1307
|
raise ValueError(f'Invalid parameter title ({title})')
|
|
1059
1308
|
if path is not None and not isinstance(path, str):
|
|
@@ -1067,9 +1316,9 @@ def quick_imshow(a, title=None, path=None, name=None, save_fig=False, save_only=
|
|
|
1067
1316
|
if not isinstance(block, bool):
|
|
1068
1317
|
raise ValueError(f'Invalid parameter block ({block})')
|
|
1069
1318
|
if not title:
|
|
1070
|
-
title='quick imshow'
|
|
1319
|
+
title = 'quick imshow'
|
|
1071
1320
|
if name is None:
|
|
1072
|
-
ttitle = re_sub(r
|
|
1321
|
+
ttitle = re_sub(r'\s+', '_', title)
|
|
1073
1322
|
if path is None:
|
|
1074
1323
|
path = f'{ttitle}.png'
|
|
1075
1324
|
else:
|
|
@@ -1079,12 +1328,15 @@ def quick_imshow(a, title=None, path=None, name=None, save_fig=False, save_only=
|
|
|
1079
1328
|
path = name
|
|
1080
1329
|
else:
|
|
1081
1330
|
path = f'{path}/{name}'
|
|
1082
|
-
if 'cmap' in kwargs and a.ndim == 3
|
|
1331
|
+
if ('cmap' in kwargs and a.ndim == 3
|
|
1332
|
+
and (a.shape[2] == 3 or a.shape[2] == 4)):
|
|
1083
1333
|
use_cmap = True
|
|
1084
1334
|
if a.shape[2] == 4 and a[:,:,-1].min() != a[:,:,-1].max():
|
|
1085
1335
|
use_cmap = False
|
|
1086
|
-
if any(
|
|
1087
|
-
|
|
1336
|
+
if any(
|
|
1337
|
+
a[i,j,0] != a[i,j,1] and a[i,j,0] != a[i,j,2]
|
|
1338
|
+
for i in range(a.shape[0])
|
|
1339
|
+
for j in range(a.shape[1])):
|
|
1088
1340
|
use_cmap = False
|
|
1089
1341
|
if use_cmap:
|
|
1090
1342
|
a = a[:,:,0]
|
|
@@ -1119,16 +1371,22 @@ def quick_imshow(a, title=None, path=None, name=None, save_fig=False, save_only=
|
|
|
1119
1371
|
if block:
|
|
1120
1372
|
plt.show(block=block)
|
|
1121
1373
|
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1374
|
+
|
|
1375
|
+
def quick_plot(
|
|
1376
|
+
*args, xerr=None, yerr=None, vlines=None, title=None, xlim=None,
|
|
1377
|
+
ylim=None, xlabel=None, ylabel=None, legend=None, path=None, name=None,
|
|
1378
|
+
show_grid=False, save_fig=False, save_only=False, clear=True,
|
|
1379
|
+
block=False, **kwargs):
|
|
1380
|
+
"""Display a 2D line plot."""
|
|
1125
1381
|
if title is not None and not isinstance(title, str):
|
|
1126
1382
|
illegal_value(title, 'title', 'quick_plot')
|
|
1127
1383
|
title = None
|
|
1128
|
-
if xlim is not None and not isinstance(xlim, (tuple, list))
|
|
1384
|
+
if (xlim is not None and not isinstance(xlim, (tuple, list))
|
|
1385
|
+
and len(xlim) != 2):
|
|
1129
1386
|
illegal_value(xlim, 'xlim', 'quick_plot')
|
|
1130
1387
|
xlim = None
|
|
1131
|
-
if ylim is not None and not isinstance(ylim, (tuple, list))
|
|
1388
|
+
if (ylim is not None and not isinstance(ylim, (tuple, list))
|
|
1389
|
+
and len(ylim) != 2):
|
|
1132
1390
|
illegal_value(ylim, 'ylim', 'quick_plot')
|
|
1133
1391
|
ylim = None
|
|
1134
1392
|
if xlabel is not None and not isinstance(xlabel, str):
|
|
@@ -1161,7 +1419,7 @@ def quick_plot(*args, xerr=None, yerr=None, vlines=None, title=None, xlim=None,
|
|
|
1161
1419
|
if title is None:
|
|
1162
1420
|
title = 'quick plot'
|
|
1163
1421
|
if name is None:
|
|
1164
|
-
ttitle = re_sub(r
|
|
1422
|
+
ttitle = re_sub(r'\s+', '_', title)
|
|
1165
1423
|
if path is None:
|
|
1166
1424
|
path = f'{ttitle}.png'
|
|
1167
1425
|
else:
|
|
@@ -1186,8 +1444,8 @@ def quick_plot(*args, xerr=None, yerr=None, vlines=None, title=None, xlim=None,
|
|
|
1186
1444
|
plt.ion()
|
|
1187
1445
|
plt.figure(title)
|
|
1188
1446
|
if depth_tuple(args) > 1:
|
|
1189
|
-
|
|
1190
|
-
|
|
1447
|
+
for y in args:
|
|
1448
|
+
plt.plot(*y, **kwargs)
|
|
1191
1449
|
else:
|
|
1192
1450
|
if xerr is None and yerr is None:
|
|
1193
1451
|
plt.plot(*args, **kwargs)
|
|
@@ -1199,7 +1457,8 @@ def quick_plot(*args, xerr=None, yerr=None, vlines=None, title=None, xlim=None,
|
|
|
1199
1457
|
for v in vlines:
|
|
1200
1458
|
plt.axvline(v, color='r', linestyle='--', **kwargs)
|
|
1201
1459
|
# if vlines is not None:
|
|
1202
|
-
# for s in tuple(
|
|
1460
|
+
# for s in tuple(
|
|
1461
|
+
# ([x, x], list(plt.gca().get_ylim())) for x in vlines):
|
|
1203
1462
|
# plt.plot(*s, color='red', **kwargs)
|
|
1204
1463
|
if xlim is not None:
|
|
1205
1464
|
plt.xlim(xlim)
|
|
@@ -1211,7 +1470,7 @@ def quick_plot(*args, xerr=None, yerr=None, vlines=None, title=None, xlim=None,
|
|
|
1211
1470
|
plt.ylabel(ylabel)
|
|
1212
1471
|
if show_grid:
|
|
1213
1472
|
ax = plt.gca()
|
|
1214
|
-
ax.grid(color='k')
|
|
1473
|
+
ax.grid(color='k') # , linewidth=1)
|
|
1215
1474
|
if legend is not None:
|
|
1216
1475
|
plt.legend(legend)
|
|
1217
1476
|
if save_only:
|
|
@@ -1220,6 +1479,4 @@ def quick_plot(*args, xerr=None, yerr=None, vlines=None, title=None, xlim=None,
|
|
|
1220
1479
|
else:
|
|
1221
1480
|
if save_fig:
|
|
1222
1481
|
plt.savefig(path)
|
|
1223
|
-
|
|
1224
|
-
plt.show(block=block)
|
|
1225
|
-
|
|
1482
|
+
plt.show(block=block)
|