ChessAnalysisPipeline 0.0.5__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.

Files changed (41) hide show
  1. CHAP/TaskManager.py +214 -0
  2. CHAP/common/models/integration.py +392 -249
  3. CHAP/common/models/map.py +350 -198
  4. CHAP/common/processor.py +227 -189
  5. CHAP/common/reader.py +52 -39
  6. CHAP/common/utils/fit.py +1197 -991
  7. CHAP/common/utils/general.py +629 -372
  8. CHAP/common/utils/material.py +158 -121
  9. CHAP/common/utils/scanparsers.py +735 -339
  10. CHAP/common/writer.py +31 -25
  11. CHAP/edd/models.py +63 -49
  12. CHAP/edd/processor.py +130 -109
  13. CHAP/edd/reader.py +1 -1
  14. CHAP/edd/writer.py +1 -1
  15. CHAP/inference/processor.py +35 -28
  16. CHAP/inference/reader.py +1 -1
  17. CHAP/inference/writer.py +1 -1
  18. CHAP/pipeline.py +14 -28
  19. CHAP/processor.py +44 -75
  20. CHAP/reader.py +49 -40
  21. CHAP/runner.py +73 -32
  22. CHAP/saxswaxs/processor.py +1 -1
  23. CHAP/saxswaxs/reader.py +1 -1
  24. CHAP/saxswaxs/writer.py +1 -1
  25. CHAP/server.py +130 -0
  26. CHAP/sin2psi/processor.py +1 -1
  27. CHAP/sin2psi/reader.py +1 -1
  28. CHAP/sin2psi/writer.py +1 -1
  29. CHAP/tomo/__init__.py +1 -4
  30. CHAP/tomo/models.py +53 -31
  31. CHAP/tomo/processor.py +1326 -900
  32. CHAP/tomo/reader.py +4 -2
  33. CHAP/tomo/writer.py +4 -2
  34. CHAP/writer.py +47 -41
  35. {ChessAnalysisPipeline-0.0.5.dist-info → ChessAnalysisPipeline-0.0.6.dist-info}/METADATA +1 -1
  36. ChessAnalysisPipeline-0.0.6.dist-info/RECORD +52 -0
  37. ChessAnalysisPipeline-0.0.5.dist-info/RECORD +0 -50
  38. {ChessAnalysisPipeline-0.0.5.dist-info → ChessAnalysisPipeline-0.0.6.dist-info}/LICENSE +0 -0
  39. {ChessAnalysisPipeline-0.0.5.dist-info → ChessAnalysisPipeline-0.0.6.dist-info}/WHEEL +0 -0
  40. {ChessAnalysisPipeline-0.0.5.dist-info → ChessAnalysisPipeline-0.0.6.dist-info}/entry_points.txt +0 -0
  41. {ChessAnalysisPipeline-0.0.5.dist-info → ChessAnalysisPipeline-0.0.6.dist-info}/top_level.txt +0 -0
@@ -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
- Created on Mon Dec 6 15:36:22 2021
9
-
10
- @author: rv43
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
- from logging import getLogger
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
- def depth_list(L): return isinstance(L, list) and max(map(depth_list, L))+1
30
- def depth_tuple(T): return isinstance(T, tuple) and max(map(depth_tuple, T))+1
31
- def unwrap_tuple(T):
32
- if depth_tuple(T) > 1 and len(T) == 1:
33
- T = unwrap_tuple(*T)
34
- return T
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 = f'Illegal value for {name} {location}({value}, {type(value)})'
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
- def illegal_combination(value1, name1, value2, name2, location=None, raise_error=False,
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
- f'({value1}, {type(value1)} and {value2}, {type(value2)})'
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
- f'({value1}, {type(value1)} and {value2}, {type(value2)})'
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
- def test_ge_gt_le_lt(ge, gt, le, lt, func, location=None, raise_error=False, log=True):
68
- """Check individual and mutual validity of ge, gt, le, lt qualifiers
69
- func: is_int or is_num to test for int or numbers
70
- Return: True upon success or False when mutually exlusive
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
- elif lt is not None and ge >= lt:
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
- elif lt is not None and gt >= lt:
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
- """Return a range string representation matching the ge, gt, le, lt qualifiers
112
- Does not validate the inputs, do that as needed before calling
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
- """Value is an integer in range ge <= v <= le or gt < v < lt or some combination.
139
- Return: True if yes or False is no
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
- """Value is a number in range ge <= v <= le or gt < v < lt or some combination.
145
- Return: True if yes or False is no
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
- def _is_int_or_num(v, type_str, ge=None, gt=None, le=None, lt=None, raise_error=False,
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(ge, gt, le, lt, is_int, '_is_int_or_num', raise_error, log):
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(ge, gt, le, lt, is_num, '_is_int_or_num', raise_error, log):
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
- def is_int_pair(v, ge=None, gt=None, le=None, lt=None, raise_error=False, log=True):
190
- """Value is an integer pair, each in range ge <= v[i] <= le or gt < v[i] < lt or
191
- ge[i] <= v[i] <= le[i] or gt[i] < v[i] < lt[i] or some combination.
192
- Return: True if yes or False is no
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
- def is_num_pair(v, ge=None, gt=None, le=None, lt=None, raise_error=False, log=True):
197
- """Value is a number pair, each in range ge <= v[i] <= le or gt < v[i] < lt or
198
- ge[i] <= v[i] <= le[i] or gt[i] < v[i] < lt[i] or some combination.
199
- Return: True if yes or False is no
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
- def _is_int_or_num_pair(v, type_str, ge=None, gt=None, le=None, lt=None, raise_error=False,
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 and isinstance(v[0], int) and
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 and isinstance(v[0], (int, float)) and
213
- isinstance(v[1], (int, float))):
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(type_str, 'type_str', '_is_int_or_num_pair', raise_error, log)
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(ge, type_str, raise_error=raise_error, log=log):
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(gt, type_str, raise_error=raise_error, log=log):
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(le, type_str, raise_error=raise_error, log=log):
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(lt, type_str, raise_error=raise_error, log=log):
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) or
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
- def is_int_series(l, ge=None, gt=None, le=None, lt=None, raise_error=False, log=True):
244
- """Value is a tuple or list of integers, each in range ge <= l[i] <= le or
245
- gt < l[i] < lt or some combination.
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(ge, gt, le, lt, is_int, 'is_int_series', raise_error, log):
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(l, (tuple, list)):
250
- illegal_value(l, 'l', 'is_int_series', raise_error, log)
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(True if not is_int(v, ge, gt, le, lt, raise_error, log) else False for v in l):
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
- def is_num_series(l, ge=None, gt=None, le=None, lt=None, raise_error=False, log=True):
257
- """Value is a tuple or list of numbers, each in range ge <= l[i] <= le or
258
- gt < l[i] < lt or some combination.
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
- if not test_ge_gt_le_lt(ge, gt, le, lt, is_int, 'is_int_series', raise_error, log):
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(l, (tuple, list)):
263
- illegal_value(l, 'l', 'is_num_series', raise_error, log)
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(True if not is_num(v, ge, gt, le, lt, raise_error, log) else False for v in l):
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
- def is_str_series(l, raise_error=False, log=True):
270
- """Value is a tuple or list of strings.
344
+
345
+ def is_str_series(t_or_l, raise_error=False, log=True):
271
346
  """
272
- if (not isinstance(l, (tuple, list)) or
273
- any(True if not isinstance(s, str) else False for s in l)):
274
- illegal_value(l, 'l', 'is_str_series', raise_error, log)
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
- def is_dict_series(l, raise_error=False, log=True):
279
- """Value is a tuple or list of dictionaries.
355
+
356
+ def is_dict_series(t_or_l, raise_error=False, log=True):
280
357
  """
281
- if (not isinstance(l, (tuple, list)) or
282
- any(True if not isinstance(d, dict) else False for d in l)):
283
- illegal_value(l, 'l', 'is_dict_series', raise_error, log)
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
- def is_dict_nums(l, raise_error=False, log=True):
288
- """Value is a dictionary with single number values
366
+
367
+ def is_dict_nums(d, raise_error=False, log=True):
289
368
  """
290
- if (not isinstance(l, dict) or
291
- any(True if not is_num(v, log=False) else False for v in l.values())):
292
- illegal_value(l, 'l', 'is_dict_nums', raise_error, log)
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
- def is_dict_strings(l, raise_error=False, log=True):
297
- """Value is a dictionary with single string values
377
+
378
+ def is_dict_strings(d, raise_error=False, log=True):
298
379
  """
299
- if (not isinstance(l, dict) or
300
- any(True if not isinstance(v, str) else False for v in l.values())):
301
- illegal_value(l, 'l', 'is_dict_strings', raise_error, log)
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
- """Value is an array index in range ge <= v < lt.
307
- NOTE lt IS NOT included!
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(ge, 'ge', lt, 'lt', 'is_index', raise_error, log)
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
- """Value is an array index range in range ge <= v[0] <= v[1] <= le or ge <= v[0] <= v[1] < lt.
317
- NOTE le IS included!
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(ge, None, le, lt, is_int, 'is_index_range', raise_error, log):
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) or (lt is not None and v[1] >= lt):
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 = f'Value {v} out of range: !({ge} <= {v[0]} <= {v[1]} <= {le})'
415
+ error_msg = \
416
+ f'Value {v} out of range: !({ge} <= {v[0]} <= {v[1]} <= {le})'
326
417
  else:
327
- error_msg = f'Value {v} out of range: !({ge} <= {v[0]} <= {v[1]} < {lt})'
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(f'Invalid array dimension for parameter a ({a.ndim}, {a})')
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(f'Invalid array dimension for parameter a ({a.ndim}, {a})')
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(f'Invalid array dimension for parameter a ({a.ndim}, {a})')
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
- else:
365
- return type(x)(round(x, n-1-int(np.floor(np.log10(abs(x))))))
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
- xr = round_to_n(x, n)
369
- if abs(x/xr) > 1.0:
370
- xr += np.sign(x)*10**(np.floor(np.log10(abs(x)))+1-n)
371
- return type(x)(xr)
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
- xr = round_to_n(x, n)
375
- if abs(xr/x) > 1.0:
376
- xr -= np.sign(x)*10**(np.floor(np.log10(abs(x)))+1-n)
377
- return type(x)(xr)
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+1)
382
- else:
383
- raise ValueError(f'Invalid value for a or b in almost_equal (a: {a}, {type(a)}, '+
384
- f'b: {b}, {type(b)})')
385
- return False
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
- """Return a list of numbers by splitting/expanding a string on any combination of
389
- commas, whitespaces, or dashes (when split_on_dash=True)
390
- e.g: '1, 3, 5-8, 12 ' -> [1, 3, 5, 6, 7, 8, 12]
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 len(s):
507
+ if not s:
396
508
  return []
397
509
  try:
398
- ll = [x for x in re_split('\s+,\s+|\s+,|,\s+|\s+|,', s.strip())]
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
- l = []
404
- for l1 in ll:
405
- l2 = [literal_eval(x) for x in re_split('\s+-\s+|\s+-|-\s+|\s+|-', l1)]
406
- if len(l2) == 1:
407
- l += l2
408
- elif len(l2) == 2 and l2[1] > l2[0]:
409
- l += [i for i in range(l2[0], l2[1]+1)]
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, RecursionError):
526
+ except (ValueError, TypeError, SyntaxError, MemoryError,
527
+ RecursionError):
413
528
  return None
414
529
  else:
415
- l = [literal_eval(x) for x in ll]
530
+ l_of_i = [literal_eval(x) for x in list1]
416
531
  if remove_duplicates:
417
- l = list(dict.fromkeys(l))
532
+ l_of_i = list(dict.fromkeys(l_of_i))
418
533
  if sort:
419
- l = sorted(l)
420
- return l
534
+ l_of_i = sorted(l_of_i)
535
+ return l_of_i
536
+
421
537
 
422
538
  def get_trailing_int(string):
423
- indexRegex = re_compile(r'\d+$')
424
- mo = indexRegex.search(string)
425
- if mo is None:
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
- else:
428
- return int(mo.group())
544
+ return int(match.group())
429
545
 
430
- def input_int(s=None, ge=None, gt=None, le=None, lt=None, default=None, inset=None,
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
- return _input_int_or_num('int', s, ge, gt, le, lt, default, inset, raise_error, log)
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 _input_int_or_num(type_str, s=None, ge=None, gt=None, le=None, lt=None, default=None,
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(ge, gt, le, lt, is_int, '_input_int_or_num', raise_error, log):
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(ge, gt, le, lt, is_num, '_input_int_or_num', raise_error, log):
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(type_str, 'type_str', '_input_int_or_num', raise_error, log)
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(default, type_str, raise_error=raise_error, log=log):
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(ge, 'ge', default, 'default', '_input_int_or_num', raise_error,
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(gt, 'gt', default, 'default', '_input_int_or_num', raise_error,
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(le, 'le', default, 'default', '_input_int_or_num', raise_error,
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(lt, 'lt', default, 'default', '_input_int_or_num', raise_error,
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)) or any(True if not isinstance(i, int) else
473
- False for i in inset)):
474
- illegal_value(inset, 'inset', '_input_int_or_num', raise_error, log)
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 len(v_range):
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 len(i):
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
- raise ValueError(f'{v} not part of the set {inset}')
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(type_str, s, ge, gt, le, lt, default, inset, raise_error, log)
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
- def input_int_list(s=None, ge=None, le=None, split_on_dash=True, remove_duplicates=True,
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
- return _input_int_or_num_list('int', s, ge, le, split_on_dash, remove_duplicates, sort,
516
- raise_error, log)
517
-
518
- def input_num_list(s=None, ge=None, le=None, remove_duplicates=True, sort=True, raise_error=False,
519
- log=True):
520
- """Prompt the user to input a list of numbers and split the entered string on any combination
521
- of commas or whitespaces
522
- e.g: '1.0, 3, 5.8, 12 ' -> [1.0, 3.0, 5.8, 12.0]
523
- remove_duplicates: removes duplicates if True (may also change the order)
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('num', s, ge, le, False, remove_duplicates, sort, raise_error,
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
- def _input_int_or_num_list(type_str, s=None, ge=None, le=None, split_on_dash=True,
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
- #FIX do we want a limit on max dimension?
678
+ # RV do we want a limit on max dimension?
533
679
  if type_str == 'int':
534
- if not test_ge_gt_le_lt(ge, None, le, None, is_int, 'input_int_or_num_list', raise_error,
535
- log):
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(ge, None, le, None, is_num, 'input_int_or_num_list', raise_error,
539
- log):
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 len(v_range):
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
- l = string_to_list(input(), split_on_dash, remove_duplicates, sort)
700
+ _list = string_to_list(input(), split_on_dash, remove_duplicates, sort)
553
701
  except (ValueError, TypeError, SyntaxError, MemoryError, RecursionError):
554
- l = None
702
+ _list = None
555
703
  except:
556
704
  print('Unexpected error')
557
705
  raise
558
- if (not isinstance(l, list) or
559
- any(True if not _is_int_or_num(v, type_str, ge=ge, le=le) else False for v in l)):
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 separated integers '+
562
- 'e.g. 1 3,5-8 , 12')
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 separated integers '+
565
- 'e.g. 1 3,5 8 , 12')
566
- l = _input_int_or_num_list(type_str, s, ge, le, split_on_dash, remove_duplicates, sort,
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 l
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 len(i):
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
- if not isinstance(items, (tuple, list)) or any(True if not isinstance(i, str) else False
604
- for i in items):
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(f'Invalid value for default ({default}), must be in {items}')
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)+1}]'
765
+ default_string = f' [{1+items.index(default)}]'
612
766
  else:
613
767
  default_string = ''
614
768
  if header is None:
615
- print(f'Choose one of the following items (1, {len(items)}){default_string}:')
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 = input()
622
- if isinstance(choice, str) and not len(choice):
776
+ choice = input()
777
+ if isinstance(choice, str) and not choice:
623
778
  choice = items.index(default)
624
- print(f'{choice+1}')
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
- def assert_no_duplicates_in_list_of_dicts(l: list, raise_error=False) -> list:
642
- if not isinstance(l, list):
643
- illegal_value(l, 'l', 'assert_no_duplicates_in_list_of_dicts', raise_error)
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(True if not isinstance(d, dict) else False for d in l):
646
- illegal_value(l, 'l', 'assert_no_duplicates_in_list_of_dicts', raise_error)
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(l) != len([dict(t) for t in {tuple(sorted(d.items())) for d in l}]):
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 {l}')
651
- else:
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
- else:
655
- return l
817
+ return _list
818
+
656
819
 
657
- def assert_no_duplicate_key_in_list_of_dicts(l: list, key: str, raise_error=False) -> list:
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(key, 'key', 'assert_no_duplicate_key_in_list_of_dicts', raise_error)
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(l, list):
662
- illegal_value(l, 'l', 'assert_no_duplicate_key_in_list_of_dicts', raise_error)
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(True if not isinstance(d, dict) else False for d in l):
665
- illegal_value(l, 'l', 'assert_no_duplicates_in_list_of_dicts', raise_error)
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 l]
668
- if None in keys or len(set(keys)) != len(l):
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(f'Duplicate or missing key ({key}) found in {l}')
671
- else:
672
- logger.error(f'Duplicate or missing key ({key}) found in {l}')
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
- else:
675
- return l
846
+ return _list
676
847
 
677
- def assert_no_duplicate_attr_in_list_of_objs(l: list, attr: str, raise_error=False) -> list:
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(attr, 'attr', 'assert_no_duplicate_attr_in_list_of_objs', raise_error)
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(l, list):
682
- illegal_value(l, 'l', 'assert_no_duplicate_key_in_list_of_objs', raise_error)
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 l]
685
- if None in attrs or len(set(attrs)) != len(l):
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(f'Duplicate or missing attr ({attr}) found in {l}')
688
- else:
689
- logger.error(f'Duplicate or missing attr ({attr}) found in {l}')
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
- else:
692
- return l
693
-
694
- def file_exists_and_readable(path):
695
- import os
696
- if not os.path.isfile(path):
697
- raise ValueError(f'{path} is not a valid file')
698
- elif not os.access(path, os.R_OK):
699
- raise ValueError(f'{path} is not accessible for reading')
700
- else:
701
- return path
702
-
703
- def draw_mask_1d(ydata, xdata=None, current_index_ranges=None, current_mask=None,
704
- select_mask=True, num_index_ranges_max=None, title=None, legend=None, test_mode=False):
705
- #FIX make color blind friendly
706
- def draw_selections(ax, current_include, current_exclude, selected_index_ranges):
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 (low, upp) in current_include:
712
- xlow = 0.5*(xdata[max(0, low-1)]+xdata[low])
713
- xupp = 0.5*(xdata[upp]+xdata[min(num_data-1, upp+1)])
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 (low, upp) in current_exclude:
716
- xlow = 0.5*(xdata[max(0, low-1)]+xdata[low])
717
- xupp = 0.5*(xdata[upp]+xdata[min(num_data-1, upp+1)])
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 (low, upp) in selected_index_ranges:
720
- xlow = 0.5*(xdata[max(0, low-1)]+xdata[low])
721
- xupp = 0.5*(xdata[upp]+xdata[min(num_data-1, upp+1)])
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
- if len(selected_index_ranges) > 0:
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] = (selected_index_ranges[-1], event.xdata)
921
+ selected_index_ranges[-1] = \
922
+ (selected_index_ranges[-1], event.xdata)
736
923
  else:
737
- selected_index_ranges[-1] = (event.xdata, selected_index_ranges[-1])
738
- draw_selections(event.inaxes, current_include, current_exclude, selected_index_ranges)
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
- if len(selected_index_ranges):
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 len(current_include):
941
+ while current_include:
750
942
  current_include.pop()
751
- while len(current_exclude):
943
+ while current_exclude:
752
944
  current_exclude.pop()
753
945
  selected_mask.fill(False)
754
- draw_selections(ax, current_include, current_exclude, selected_index_ranges)
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
- for (low, upp) in selected_index_ranges:
758
- selected_mask = np.logical_and(xdata >= xdata[low], xdata <= xdata[upp])
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 (low, upp) in unselected_index_ranges:
761
- unselected_mask = np.logical_and(xdata >= xdata[low], xdata <= xdata[upp])
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
- # Update the currently included index ranges (where mask is True)
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 == True:
770
- if len(current_include) == 0 or type(current_include[-1]) == tuple:
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 len(current_include) > 0 and isinstance(current_include[-1], int):
972
+ if current_include and isinstance(current_include[-1], int):
774
973
  current_include[-1] = (current_include[-1], i-1)
775
- if len(current_include) > 0 and isinstance(current_include[-1], int):
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('Invalid current_index_ranges parameter ({current_index_ranges}, '+
798
- f'{type(current_index_ranges)})')
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('Invalid select_mask parameter ({select_mask}, {type(select_mask)})')
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('num_index_ranges_max input not yet implemented in draw_mask_1d')
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
- illegal(title, 'title')
1011
+ illegal_value(title, 'title')
809
1012
  title = ''
810
1013
  if legend is None and not isinstance(title, str):
811
- illegal(legend, 'legend')
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 ranges as needed
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 len(current_index_ranges):
833
- current_index_ranges = sorted([(low, upp) for (low, upp) in current_index_ranges])
834
- for (low, upp) in current_index_ranges:
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
- if low < 0:
838
- low = 0
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(selected_mask, selected_index_ranges, unselected_index_ranges)
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 len(current_include):
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((current_include[i-1][1]+1, current_include[i][0]-1))
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]+1, num_data-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(ax, current_include, current_exclude, selected_index_ranges)
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 = unselected_index_ranges, \
895
- selected_index_ranges
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(selected_mask, selected_index_ranges, unselected_index_ranges)
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
- def select_image_bounds(a, axis, low=None, upp=None, num_min=None, title='select array bounds',
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
- """Interactively select the lower and upper data bounds for a 2D numpy array.
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(a.ndim, 'array dimension', location='select_image_bounds',
910
- raise_error=raise_error)
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(axis, 'axis', location='select_image_bounds', raise_error=raise_error)
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('Invalid input for num_min in select_image_bounds, input ignored')
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(a[:,min_:max_], title=title, aspect='auto',
931
- extent=[min_,max_,a.shape[0],0])
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(a[min_:max_,:], title=title, aspect='auto',
934
- extent=[0,a.shape[1], max_,min_])
935
- zoom_flag = input_yesno('Set lower data bound (y) or zoom in (n)?', 'y')
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
- else:
940
- min_ = input_int(' Set lower zoom index', ge=0, le=low_max)
941
- max_ = input_int(' Set upper zoom index', ge=min_+1, le=low_max+1)
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(low, 'low', location='select_image_bounds', raise_error=raise_error)
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(a[:,min_:max_], title=title, aspect='auto',
953
- extent=[min_,max_,a.shape[0],0])
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(a[min_:max_,:], title=title, aspect='auto',
956
- extent=[0,a.shape[1], max_,min_])
957
- zoom_flag = input_yesno('Set upper data bound (y) or zoom in (n)?', 'y')
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(' Set upper data bound', ge=upp_min, le=a.shape[axis])
1184
+ upp = input_int(
1185
+ ' Set upper data bound', ge=upp_min, le=a.shape[axis])
960
1186
  break
961
- else:
962
- min_ = input_int(' Set upper zoom index', ge=upp_min, le=a.shape[axis]-1)
963
- max_ = input_int(' Set upper zoom index', ge=min_+1, le=a.shape[axis])
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(upp, 'upp', location='select_image_bounds', raise_error=raise_error)
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(a, axis, low=low_save, upp=upp_save, num_min=num_min_save,
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
- def select_one_image_bound(a, axis, bound=None, bound_name=None, title='select array bounds',
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
- """Interactively select a data boundary for a 2D numpy array.
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(a.ndim, 'array dimension', location='select_one_image_bound',
993
- raise_error=raise_error)
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(axis, 'axis', location='select_one_image_bound', raise_error=raise_error)
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(a[:,min_:max_], title=title, aspect='auto',
1007
- extent=[min_,max_,a.shape[0],0])
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(a[min_:max_,:], title=title, aspect='auto',
1010
- extent=[0,a.shape[1], max_,min_])
1011
- zoom_flag = input_yesno(f'Set {bound_name} (y) or zoom in (n)?', 'y')
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
- else:
1017
- min_ = input_int(' Set lower zoom index', ge=0, le=bound_max)
1018
- max_ = input_int(' Set upper zoom index', ge=min_+1, le=bound_max+1)
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(bound, 'bound', location='select_one_image_bound', raise_error=raise_error)
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(a, axis, bound_name=bound_name, title=title)
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
- def quick_imshow(a, title=None, path=None, name=None, save_fig=False, save_only=False,
1055
- clear=True, extent=None, show_grid=False, grid_color='w', grid_linewidth=1,
1056
- block=False, **kwargs):
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"\s+", '_', title)
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 and (a.shape[2] == 3 or a.shape[2] == 4):
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(True if a[i,j,0] != a[i,j,1] and a[i,j,0] != a[i,j,2] else False
1087
- for i in range(a.shape[0]) for j in range(a.shape[1])):
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
- def quick_plot(*args, xerr=None, yerr=None, vlines=None, title=None, xlim=None, ylim=None,
1123
- xlabel=None, ylabel=None, legend=None, path=None, name=None, show_grid=False,
1124
- save_fig=False, save_only=False, clear=True, block=False, **kwargs):
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)) and len(xlim) != 2:
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)) and len(ylim) != 2:
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"\s+", '_', title)
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
- for y in args:
1190
- plt.plot(*y, **kwargs)
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(([x, x], list(plt.gca().get_ylim())) for x in vlines):
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')#, linewidth=1)
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
- if block:
1224
- plt.show(block=block)
1225
-
1482
+ plt.show(block=block)