Mercurial > repos > rv43 > tomo
comparison general.py @ 69:fba792d5f83b draft
planemo upload for repository https://github.com/rolfverberg/galaxytools commit ab9f412c362a4ab986d00e21d5185cfcf82485c1
author | rv43 |
---|---|
date | Fri, 10 Mar 2023 16:02:04 +0000 |
parents | ba5866d0251d |
children |
comparison
equal
deleted
inserted
replaced
68:ba5866d0251d | 69:fba792d5f83b |
---|---|
1 #!/usr/bin/env python3 | 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 | |
2 | 5 |
3 # -*- coding: utf-8 -*- | 6 # -*- coding: utf-8 -*- |
4 """ | 7 """ |
5 Created on Mon Dec 6 15:36:22 2021 | 8 Created on Mon Dec 6 15:36:22 2021 |
6 | 9 |
7 @author: rv43 | 10 @author: rv43 |
8 """ | 11 """ |
9 | 12 |
10 import logging | 13 import logging |
14 logger=logging.getLogger(__name__) | |
11 | 15 |
12 import os | 16 import os |
13 import sys | 17 import sys |
14 import re | 18 import re |
15 import yaml | 19 try: |
20 from yaml import safe_load, safe_dump | |
21 except: | |
22 pass | |
16 try: | 23 try: |
17 import h5py | 24 import h5py |
18 except: | 25 except: |
19 pass | 26 pass |
20 import numpy as np | 27 import numpy as np |
21 try: | 28 try: |
22 import matplotlib.pyplot as plt | 29 import matplotlib.pyplot as plt |
30 import matplotlib.lines as mlines | |
31 from matplotlib import transforms | |
23 from matplotlib.widgets import Button | 32 from matplotlib.widgets import Button |
24 except: | 33 except: |
25 pass | 34 pass |
26 | 35 |
27 from ast import literal_eval | 36 from ast import literal_eval |
37 try: | |
38 from asteval import Interpreter, get_ast_names | |
39 except: | |
40 pass | |
28 from copy import deepcopy | 41 from copy import deepcopy |
42 try: | |
43 from sympy import diff, simplify | |
44 except: | |
45 pass | |
29 from time import time | 46 from time import time |
30 | 47 |
31 | 48 |
32 def depth_list(L): return isinstance(L, list) and max(map(depth_list, L))+1 | 49 def depth_list(L): return(isinstance(L, list) and max(map(depth_list, L))+1) |
33 def depth_tuple(T): return isinstance(T, tuple) and max(map(depth_tuple, T))+1 | 50 def depth_tuple(T): return(isinstance(T, tuple) and max(map(depth_tuple, T))+1) |
34 def unwrap_tuple(T): | 51 def unwrap_tuple(T): |
35 if depth_tuple(T) > 1 and len(T) == 1: | 52 if depth_tuple(T) > 1 and len(T) == 1: |
36 T = unwrap_tuple(*T) | 53 T = unwrap_tuple(*T) |
37 return T | 54 return(T) |
38 | 55 |
39 def illegal_value(value, name, location=None, exit_flag=False): | 56 def illegal_value(value, name, location=None, raise_error=False, log=True): |
40 if not isinstance(location, str): | 57 if not isinstance(location, str): |
41 location = '' | 58 location = '' |
42 else: | 59 else: |
43 location = f'in {location} ' | 60 location = f'in {location} ' |
44 if isinstance(name, str): | 61 if isinstance(name, str): |
45 logging.error(f'Illegal value for {name} {location}({value}, {type(value)})') | 62 error_msg = f'Illegal value for {name} {location}({value}, {type(value)})' |
46 else: | 63 else: |
47 logging.error(f'Illegal value {location}({value}, {type(value)})') | 64 error_msg = f'Illegal value {location}({value}, {type(value)})' |
48 if exit_flag: | 65 if log: |
49 raise ValueError | 66 logger.error(error_msg) |
50 | 67 if raise_error: |
51 def is_int(v, v_min=None, v_max=None): | 68 raise ValueError(error_msg) |
52 """Value is an integer in range v_min <= v <= v_max. | 69 |
53 """ | 70 def illegal_combination(value1, name1, value2, name2, location=None, raise_error=False, |
54 if not isinstance(v, int): | 71 log=True): |
55 return False | 72 if not isinstance(location, str): |
56 if v_min is not None and not isinstance(v_min, int): | 73 location = '' |
57 illegal_value(v_min, 'v_min', 'is_int') | 74 else: |
58 return False | 75 location = f'in {location} ' |
59 if v_max is not None and not isinstance(v_max, int): | 76 if isinstance(name1, str): |
60 illegal_value(v_max, 'v_max', 'is_int') | 77 error_msg = f'Illegal combination for {name1} and {name2} {location}'+ \ |
61 return False | 78 f'({value1}, {type(value1)} and {value2}, {type(value2)})' |
62 if v_min is not None and v_max is not None and v_min > v_max: | 79 else: |
63 logging.error(f'Illegal v_min, v_max combination ({v_min}, {v_max})') | 80 error_msg = f'Illegal combination {location}'+ \ |
64 return False | 81 f'({value1}, {type(value1)} and {value2}, {type(value2)})' |
65 if (v_min is not None and v < v_min) or (v_max is not None and v > v_max): | 82 if log: |
66 return False | 83 logger.error(error_msg) |
67 return True | 84 if raise_error: |
68 | 85 raise ValueError(error_msg) |
69 def is_int_pair(v, v_min=None, v_max=None): | 86 |
70 """Value is an integer pair, each in range v_min <= v[i] <= v_max or | 87 def test_ge_gt_le_lt(ge, gt, le, lt, func, location=None, raise_error=False, log=True): |
71 v_min[i] <= v[i] <= v_max[i]. | 88 """Check individual and mutual validity of ge, gt, le, lt qualifiers |
72 """ | 89 func: is_int or is_num to test for int or numbers |
73 if not (isinstance(v, (tuple, list)) and len(v) == 2 and isinstance(v[0], int) and | 90 Return: True upon success or False when mutually exlusive |
74 isinstance(v[1], int)): | 91 """ |
75 return False | 92 if ge is None and gt is None and le is None and lt is None: |
76 if v_min is not None or v_max is not None: | 93 return(True) |
77 if (v_min is None or isinstance(v_min, int)) and (v_max is None or isinstance(v_max, int)): | 94 if ge is not None: |
78 if True in [True if not is_int(vi, v_min=v_min, v_max=v_max) else False for vi in v]: | 95 if not func(ge): |
79 return False | 96 illegal_value(ge, 'ge', location, raise_error, log) |
80 elif is_int_pair(v_min) and is_int_pair(v_max): | 97 return(False) |
81 if True in [True if v_min[i] > v_max[i] else False for i in range(2)]: | 98 if gt is not None: |
82 logging.error(f'Illegal v_min, v_max combination ({v_min}, {v_max})') | 99 illegal_combination(ge, 'ge', gt, 'gt', location, raise_error, log) |
83 return False | 100 return(False) |
84 if True in [True if not is_int(v[i], v_min[i], v_max[i]) else False for i in range(2)]: | 101 elif gt is not None and not func(gt): |
85 return False | 102 illegal_value(gt, 'gt', location, raise_error, log) |
86 elif is_int_pair(v_min) and (v_max is None or isinstance(v_max, int)): | 103 return(False) |
87 if True in [True if not is_int(v[i], v_min=v_min[i], v_max=v_max) else False | 104 if le is not None: |
88 for i in range(2)]: | 105 if not func(le): |
89 return False | 106 illegal_value(le, 'le', location, raise_error, log) |
90 elif (v_min is None or isinstance(v_min, int)) and is_int_pair(v_max): | 107 return(False) |
91 if True in [True if not is_int(v[i], v_min=v_min, v_max=v_max[i]) else False | 108 if lt is not None: |
92 for i in range(2)]: | 109 illegal_combination(le, 'le', lt, 'lt', location, raise_error, log) |
93 return False | 110 return(False) |
94 else: | 111 elif lt is not None and not func(lt): |
95 logging.error(f'Illegal v_min or v_max input ({v_min} {type(v_min)} and '+ | 112 illegal_value(lt, 'lt', location, raise_error, log) |
96 f'{v_max} {type(v_max)})') | 113 return(False) |
97 return False | 114 if ge is not None: |
98 return True | 115 if le is not None and ge > le: |
99 | 116 illegal_combination(ge, 'ge', le, 'le', location, raise_error, log) |
100 def is_int_series(l, v_min=None, v_max=None): | 117 return(False) |
101 """Value is a tuple or list of integers, each in range v_min <= l[i] <= v_max. | 118 elif lt is not None and ge >= lt: |
102 """ | 119 illegal_combination(ge, 'ge', lt, 'lt', location, raise_error, log) |
103 if v_min is not None and not isinstance(v_min, int): | 120 return(False) |
104 illegal_value(v_min, 'v_min', 'is_int_series') | 121 elif gt is not None: |
105 return False | 122 if le is not None and gt >= le: |
106 if v_max is not None and not isinstance(v_max, int): | 123 illegal_combination(gt, 'gt', le, 'le', location, raise_error, log) |
107 illegal_value(v_max, 'v_max', 'is_int_series') | 124 return(False) |
108 return False | 125 elif lt is not None and gt >= lt: |
126 illegal_combination(gt, 'gt', lt, 'lt', location, raise_error, log) | |
127 return(False) | |
128 return(True) | |
129 | |
130 def range_string_ge_gt_le_lt(ge=None, gt=None, le=None, lt=None): | |
131 """Return a range string representation matching the ge, gt, le, lt qualifiers | |
132 Does not validate the inputs, do that as needed before calling | |
133 """ | |
134 range_string = '' | |
135 if ge is not None: | |
136 if le is None and lt is None: | |
137 range_string += f'>= {ge}' | |
138 else: | |
139 range_string += f'[{ge}, ' | |
140 elif gt is not None: | |
141 if le is None and lt is None: | |
142 range_string += f'> {gt}' | |
143 else: | |
144 range_string += f'({gt}, ' | |
145 if le is not None: | |
146 if ge is None and gt is None: | |
147 range_string += f'<= {le}' | |
148 else: | |
149 range_string += f'{le}]' | |
150 elif lt is not None: | |
151 if ge is None and gt is None: | |
152 range_string += f'< {lt}' | |
153 else: | |
154 range_string += f'{lt})' | |
155 return(range_string) | |
156 | |
157 def is_int(v, ge=None, gt=None, le=None, lt=None, raise_error=False, log=True): | |
158 """Value is an integer in range ge <= v <= le or gt < v < lt or some combination. | |
159 Return: True if yes or False is no | |
160 """ | |
161 return(_is_int_or_num(v, 'int', ge, gt, le, lt, raise_error, log)) | |
162 | |
163 def is_num(v, ge=None, gt=None, le=None, lt=None, raise_error=False, log=True): | |
164 """Value is a number in range ge <= v <= le or gt < v < lt or some combination. | |
165 Return: True if yes or False is no | |
166 """ | |
167 return(_is_int_or_num(v, 'num', ge, gt, le, lt, raise_error, log)) | |
168 | |
169 def _is_int_or_num(v, type_str, ge=None, gt=None, le=None, lt=None, raise_error=False, | |
170 log=True): | |
171 if type_str == 'int': | |
172 if not isinstance(v, int): | |
173 illegal_value(v, 'v', '_is_int_or_num', raise_error, log) | |
174 return(False) | |
175 if not test_ge_gt_le_lt(ge, gt, le, lt, is_int, '_is_int_or_num', raise_error, log): | |
176 return(False) | |
177 elif type_str == 'num': | |
178 if not isinstance(v, (int, float)): | |
179 illegal_value(v, 'v', '_is_int_or_num', raise_error, log) | |
180 return(False) | |
181 if not test_ge_gt_le_lt(ge, gt, le, lt, is_num, '_is_int_or_num', raise_error, log): | |
182 return(False) | |
183 else: | |
184 illegal_value(type_str, 'type_str', '_is_int_or_num', raise_error, log) | |
185 return(False) | |
186 if ge is None and gt is None and le is None and lt is None: | |
187 return(True) | |
188 error = False | |
189 if ge is not None and v < ge: | |
190 error = True | |
191 error_msg = f'Value {v} out of range: {v} !>= {ge}' | |
192 if not error and gt is not None and v <= gt: | |
193 error = True | |
194 error_msg = f'Value {v} out of range: {v} !> {gt}' | |
195 if not error and le is not None and v > le: | |
196 error = True | |
197 error_msg = f'Value {v} out of range: {v} !<= {le}' | |
198 if not error and lt is not None and v >= lt: | |
199 error = True | |
200 error_msg = f'Value {v} out of range: {v} !< {lt}' | |
201 if error: | |
202 if log: | |
203 logger.error(error_msg) | |
204 if raise_error: | |
205 raise ValueError(error_msg) | |
206 return(False) | |
207 return(True) | |
208 | |
209 def is_int_pair(v, ge=None, gt=None, le=None, lt=None, raise_error=False, log=True): | |
210 """Value is an integer pair, each in range ge <= v[i] <= le or gt < v[i] < lt or | |
211 ge[i] <= v[i] <= le[i] or gt[i] < v[i] < lt[i] or some combination. | |
212 Return: True if yes or False is no | |
213 """ | |
214 return(_is_int_or_num_pair(v, 'int', ge, gt, le, lt, raise_error, log)) | |
215 | |
216 def is_num_pair(v, ge=None, gt=None, le=None, lt=None, raise_error=False, log=True): | |
217 """Value is a number pair, each in range ge <= v[i] <= le or gt < v[i] < lt or | |
218 ge[i] <= v[i] <= le[i] or gt[i] < v[i] < lt[i] or some combination. | |
219 Return: True if yes or False is no | |
220 """ | |
221 return(_is_int_or_num_pair(v, 'num', ge, gt, le, lt, raise_error, log)) | |
222 | |
223 def _is_int_or_num_pair(v, type_str, ge=None, gt=None, le=None, lt=None, raise_error=False, | |
224 log=True): | |
225 if type_str == 'int': | |
226 if not (isinstance(v, (tuple, list)) and len(v) == 2 and isinstance(v[0], int) and | |
227 isinstance(v[1], int)): | |
228 illegal_value(v, 'v', '_is_int_or_num_pair', raise_error, log) | |
229 return(False) | |
230 func = is_int | |
231 elif type_str == 'num': | |
232 if not (isinstance(v, (tuple, list)) and len(v) == 2 and isinstance(v[0], (int, float)) and | |
233 isinstance(v[1], (int, float))): | |
234 illegal_value(v, 'v', '_is_int_or_num_pair', raise_error, log) | |
235 return(False) | |
236 func = is_num | |
237 else: | |
238 illegal_value(type_str, 'type_str', '_is_int_or_num_pair', raise_error, log) | |
239 return(False) | |
240 if ge is None and gt is None and le is None and lt is None: | |
241 return(True) | |
242 if ge is None or func(ge, log=True): | |
243 ge = 2*[ge] | |
244 elif not _is_int_or_num_pair(ge, type_str, raise_error=raise_error, log=log): | |
245 return(False) | |
246 if gt is None or func(gt, log=True): | |
247 gt = 2*[gt] | |
248 elif not _is_int_or_num_pair(gt, type_str, raise_error=raise_error, log=log): | |
249 return(False) | |
250 if le is None or func(le, log=True): | |
251 le = 2*[le] | |
252 elif not _is_int_or_num_pair(le, type_str, raise_error=raise_error, log=log): | |
253 return(False) | |
254 if lt is None or func(lt, log=True): | |
255 lt = 2*[lt] | |
256 elif not _is_int_or_num_pair(lt, type_str, raise_error=raise_error, log=log): | |
257 return(False) | |
258 if (not func(v[0], ge[0], gt[0], le[0], lt[0], raise_error, log) or | |
259 not func(v[1], ge[1], gt[1], le[1], lt[1], raise_error, log)): | |
260 return(False) | |
261 return(True) | |
262 | |
263 def is_int_series(l, ge=None, gt=None, le=None, lt=None, raise_error=False, log=True): | |
264 """Value is a tuple or list of integers, each in range ge <= l[i] <= le or | |
265 gt < l[i] < lt or some combination. | |
266 """ | |
267 if not test_ge_gt_le_lt(ge, gt, le, lt, is_int, 'is_int_series', raise_error, log): | |
268 return(False) | |
109 if not isinstance(l, (tuple, list)): | 269 if not isinstance(l, (tuple, list)): |
110 return False | 270 illegal_value(l, 'l', 'is_int_series', raise_error, log) |
111 if True in [True if not is_int(v, v_min=v_min, v_max=v_max) else False for v in l]: | 271 return(False) |
112 return False | 272 if any(True if not is_int(v, ge, gt, le, lt, raise_error, log) else False for v in l): |
113 return True | 273 return(False) |
114 | 274 return(True) |
115 def is_num(v, v_min=None, v_max=None): | 275 |
116 """Value is a number in range v_min <= v <= v_max. | 276 def is_num_series(l, ge=None, gt=None, le=None, lt=None, raise_error=False, log=True): |
117 """ | 277 """Value is a tuple or list of numbers, each in range ge <= l[i] <= le or |
118 if not isinstance(v, (int, float)): | 278 gt < l[i] < lt or some combination. |
119 return False | 279 """ |
120 if v_min is not None and not isinstance(v_min, (int, float)): | 280 if not test_ge_gt_le_lt(ge, gt, le, lt, is_int, 'is_int_series', raise_error, log): |
121 illegal_value(v_min, 'v_min', 'is_num') | 281 return(False) |
122 return False | |
123 if v_max is not None and not isinstance(v_max, (int, float)): | |
124 illegal_value(v_max, 'v_max', 'is_num') | |
125 return False | |
126 if v_min is not None and v_max is not None and v_min > v_max: | |
127 logging.error(f'Illegal v_min, v_max combination ({v_min}, {v_max})') | |
128 return False | |
129 if (v_min is not None and v < v_min) or (v_max is not None and v > v_max): | |
130 return False | |
131 return True | |
132 | |
133 def is_num_pair(v, v_min=None, v_max=None): | |
134 """Value is a number pair, each in range v_min <= v[i] <= v_max or | |
135 v_min[i] <= v[i] <= v_max[i]. | |
136 """ | |
137 if not (isinstance(v, (tuple, list)) and len(v) == 2 and isinstance(v[0], (int, float)) and | |
138 isinstance(v[1], (int, float))): | |
139 return False | |
140 if v_min is not None or v_max is not None: | |
141 if ((v_min is None or isinstance(v_min, (int, float))) and | |
142 (v_max is None or isinstance(v_max, (int, float)))): | |
143 if True in [True if not is_num(vi, v_min=v_min, v_max=v_max) else False for vi in v]: | |
144 return False | |
145 elif is_num_pair(v_min) and is_num_pair(v_max): | |
146 if True in [True if v_min[i] > v_max[i] else False for i in range(2)]: | |
147 logging.error(f'Illegal v_min, v_max combination ({v_min}, {v_max})') | |
148 return False | |
149 if True in [True if not is_num(v[i], v_min[i], v_max[i]) else False for i in range(2)]: | |
150 return False | |
151 elif is_num_pair(v_min) and (v_max is None or isinstance(v_max, (int, float))): | |
152 if True in [True if not is_num(v[i], v_min=v_min[i], v_max=v_max) else False | |
153 for i in range(2)]: | |
154 return False | |
155 elif (v_min is None or isinstance(v_min, (int, float))) and is_num_pair(v_max): | |
156 if True in [True if not is_num(v[i], v_min=v_min, v_max=v_max[i]) else False | |
157 for i in range(2)]: | |
158 return False | |
159 else: | |
160 logging.error(f'Illegal v_min or v_max input ({v_min} {type(v_min)} and '+ | |
161 f'{v_max} {type(v_max)})') | |
162 return False | |
163 return True | |
164 | |
165 def is_num_series(l, v_min=None, v_max=None): | |
166 """Value is a tuple or list of numbers, each in range v_min <= l[i] <= v_max. | |
167 """ | |
168 if v_min is not None and not isinstance(v_min, (int, float)): | |
169 illegal_value(v_min, 'v_min', 'is_num_series') | |
170 return False | |
171 if v_max is not None and not isinstance(v_max, (int, float)): | |
172 illegal_value(v_max, 'v_max', 'is_num_series') | |
173 return False | |
174 if not isinstance(l, (tuple, list)): | 282 if not isinstance(l, (tuple, list)): |
175 return False | 283 illegal_value(l, 'l', 'is_num_series', raise_error, log) |
176 if True in [True if not is_num(v, v_min=v_min, v_max=v_max) else False for v in l]: | 284 return(False) |
177 return False | 285 if any(True if not is_num(v, ge, gt, le, lt, raise_error, log) else False for v in l): |
178 return True | 286 return(False) |
179 | 287 return(True) |
180 def is_index(v, v_min=0, v_max=None): | 288 |
181 """Value is an array index in range v_min <= v < v_max. | 289 def is_str_series(l, raise_error=False, log=True): |
182 NOTE v_max IS NOT included! | 290 """Value is a tuple or list of strings. |
183 """ | 291 """ |
184 if isinstance(v_max, int): | 292 if (not isinstance(l, (tuple, list)) or |
185 if v_max <= v_min: | 293 any(True if not isinstance(s, str) else False for s in l)): |
186 logging.error(f'Illegal v_min, v_max combination ({v_min}, {v_max})') | 294 illegal_value(l, 'l', 'is_str_series', raise_error, log) |
187 return False | 295 return(False) |
188 v_max -= 1 | 296 return(True) |
189 return is_int(v, v_min, v_max) | 297 |
190 | 298 def is_dict_series(l, raise_error=False, log=True): |
191 def is_index_range(v, v_min=0, v_max=None): | 299 """Value is a tuple or list of dictionaries. |
192 """Value is an array index range in range v_min <= v[0] <= v[1] <= v_max. | 300 """ |
193 NOTE v_max IS included! | 301 if (not isinstance(l, (tuple, list)) or |
194 """ | 302 any(True if not isinstance(d, dict) else False for d in l)): |
195 if not is_int_pair(v): | 303 illegal_value(l, 'l', 'is_dict_series', raise_error, log) |
196 return False | 304 return(False) |
197 if not isinstance(v_min, int): | 305 return(True) |
198 illegal_value(v_min, 'v_min', 'is_index_range') | 306 |
199 return False | 307 def is_dict_nums(l, raise_error=False, log=True): |
200 if v_max is not None: | 308 """Value is a dictionary with single number values |
201 if not isinstance(v_max, int): | 309 """ |
202 illegal_value(v_max, 'v_max', 'is_index_range') | 310 if (not isinstance(l, dict) or |
203 return False | 311 any(True if not is_num(v, log=False) else False for v in l.values())): |
204 if v_max < v_min: | 312 illegal_value(l, 'l', 'is_dict_nums', raise_error, log) |
205 logging.error(f'Illegal v_min, v_max combination ({v_min}, {v_max})') | 313 return(False) |
206 return False | 314 return(True) |
207 if not v_min <= v[0] <= v[1] or (v_max is not None and v[1] > v_max): | 315 |
208 return False | 316 def is_dict_strings(l, raise_error=False, log=True): |
209 return True | 317 """Value is a dictionary with single string values |
318 """ | |
319 if (not isinstance(l, dict) or | |
320 any(True if not isinstance(v, str) else False for v in l.values())): | |
321 illegal_value(l, 'l', 'is_dict_strings', raise_error, log) | |
322 return(False) | |
323 return(True) | |
324 | |
325 def is_index(v, ge=0, lt=None, raise_error=False, log=True): | |
326 """Value is an array index in range ge <= v < lt. | |
327 NOTE lt IS NOT included! | |
328 """ | |
329 if isinstance(lt, int): | |
330 if lt <= ge: | |
331 illegal_combination(ge, 'ge', lt, 'lt', 'is_index', raise_error, log) | |
332 return(False) | |
333 return(is_int(v, ge=ge, lt=lt, raise_error=raise_error, log=log)) | |
334 | |
335 def is_index_range(v, ge=0, le=None, lt=None, raise_error=False, log=True): | |
336 """Value is an array index range in range ge <= v[0] <= v[1] <= le or ge <= v[0] <= v[1] < lt. | |
337 NOTE le IS included! | |
338 """ | |
339 if not is_int_pair(v, raise_error=raise_error, log=log): | |
340 return(False) | |
341 if not test_ge_gt_le_lt(ge, None, le, lt, is_int, 'is_index_range', raise_error, log): | |
342 return(False) | |
343 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): | |
344 if le is not None: | |
345 error_msg = f'Value {v} out of range: !({ge} <= {v[0]} <= {v[1]} <= {le})' | |
346 else: | |
347 error_msg = f'Value {v} out of range: !({ge} <= {v[0]} <= {v[1]} < {lt})' | |
348 if log: | |
349 logger.error(error_msg) | |
350 if raise_error: | |
351 raise ValueError(error_msg) | |
352 return(False) | |
353 return(True) | |
210 | 354 |
211 def index_nearest(a, value): | 355 def index_nearest(a, value): |
212 a = np.asarray(a) | 356 a = np.asarray(a) |
213 if a.ndim > 1: | 357 if a.ndim > 1: |
214 logging.warning(f'Illegal input array ({a}, {type(a)})') | 358 raise ValueError(f'Invalid array dimension for parameter a ({a.ndim}, {a})') |
215 # Round up for .5 | 359 # Round up for .5 |
216 value *= 1.0+sys.float_info.epsilon | 360 value *= 1.0+sys.float_info.epsilon |
217 return (int)(np.argmin(np.abs(a-value))) | 361 return((int)(np.argmin(np.abs(a-value)))) |
218 | 362 |
219 def index_nearest_low(a, value): | 363 def index_nearest_low(a, value): |
220 a = np.asarray(a) | 364 a = np.asarray(a) |
221 if a.ndim > 1: | 365 if a.ndim > 1: |
222 logging.warning(f'Illegal input array ({a}, {type(a)})') | 366 raise ValueError(f'Invalid array dimension for parameter a ({a.ndim}, {a})') |
223 index = int(np.argmin(np.abs(a-value))) | 367 index = int(np.argmin(np.abs(a-value))) |
224 if value < a[index] and index > 0: | 368 if value < a[index] and index > 0: |
225 index -= 1 | 369 index -= 1 |
226 return index | 370 return(index) |
227 | 371 |
228 def index_nearest_upp(a, value): | 372 def index_nearest_upp(a, value): |
229 a = np.asarray(a) | 373 a = np.asarray(a) |
230 if a.ndim > 1: | 374 if a.ndim > 1: |
231 logging.warning(f'Illegal input array ({a}, {type(a)})') | 375 raise ValueError(f'Invalid array dimension for parameter a ({a.ndim}, {a})') |
232 index = int(np.argmin(np.abs(a-value))) | 376 index = int(np.argmin(np.abs(a-value))) |
233 if value > a[index] and index < a.size-1: | 377 if value > a[index] and index < a.size-1: |
234 index += 1 | 378 index += 1 |
235 return index | 379 return(index) |
236 | 380 |
237 def round_to_n(x, n=1): | 381 def round_to_n(x, n=1): |
238 if x == 0.0: | 382 if x == 0.0: |
239 return 0 | 383 return(0) |
240 else: | 384 else: |
241 return round(x, n-1-int(np.floor(np.log10(abs(x))))) | 385 return(type(x)(round(x, n-1-int(np.floor(np.log10(abs(x))))))) |
242 | 386 |
243 def round_up_to_n(x, n=1): | 387 def round_up_to_n(x, n=1): |
244 xr = round_to_n(x, n) | 388 xr = round_to_n(x, n) |
245 if abs(x/xr) > 1.0: | 389 if abs(x/xr) > 1.0: |
246 xr += np.sign(x)*10**(np.floor(np.log10(abs(x)))+1-n) | 390 xr += np.sign(x)*10**(np.floor(np.log10(abs(x)))+1-n) |
247 return xr | 391 return(type(x)(xr)) |
248 | 392 |
249 def trunc_to_n(x, n=1): | 393 def trunc_to_n(x, n=1): |
250 xr = round_to_n(x, n) | 394 xr = round_to_n(x, n) |
251 if abs(xr/x) > 1.0: | 395 if abs(xr/x) > 1.0: |
252 xr -= np.sign(x)*10**(np.floor(np.log10(abs(x)))+1-n) | 396 xr -= np.sign(x)*10**(np.floor(np.log10(abs(x)))+1-n) |
253 return xr | 397 return(type(x)(xr)) |
254 | 398 |
255 def string_to_list(s): | 399 def almost_equal(a, b, sig_figs): |
400 if is_num(a) and is_num(b): | |
401 return(abs(round_to_n(a-b, sig_figs)) < pow(10, -sig_figs+1)) | |
402 else: | |
403 raise ValueError(f'Invalid value for a or b in almost_equal (a: {a}, {type(a)}, '+ | |
404 f'b: {b}, {type(b)})') | |
405 return(False) | |
406 | |
407 def string_to_list(s, split_on_dash=True, remove_duplicates=True, sort=True): | |
256 """Return a list of numbers by splitting/expanding a string on any combination of | 408 """Return a list of numbers by splitting/expanding a string on any combination of |
257 dashes, commas, and/or whitespaces | 409 commas, whitespaces, or dashes (when split_on_dash=True) |
258 e.g: '1, 3, 5-8,12 ' -> [1, 3, 5, 6, 7, 8, 12] | 410 e.g: '1, 3, 5-8, 12 ' -> [1, 3, 5, 6, 7, 8, 12] |
259 """ | 411 """ |
260 if not isinstance(s, str): | 412 if not isinstance(s, str): |
261 illegal_value(s, location='string_to_list') | 413 illegal_value(s, location='string_to_list') |
262 return None | 414 return(None) |
263 if not len(s): | 415 if not len(s): |
264 return [] | 416 return([]) |
265 try: | 417 try: |
266 list1 = [x for x in re.split('\s+,\s+|\s+,|,\s+|\s+|,', s.strip())] | 418 ll = [x for x in re.split('\s+,\s+|\s+,|,\s+|\s+|,', s.strip())] |
267 except (ValueError, TypeError, SyntaxError, MemoryError, RecursionError): | 419 except (ValueError, TypeError, SyntaxError, MemoryError, RecursionError): |
268 return None | 420 return(None) |
269 try: | 421 if split_on_dash: |
270 l = [] | 422 try: |
271 for l1 in list1: | 423 l = [] |
272 l2 = [literal_eval(x) for x in re.split('\s+-\s+|\s+-|-\s+|\s+|-', l1)] | 424 for l1 in ll: |
273 if len(l2) == 1: | 425 l2 = [literal_eval(x) for x in re.split('\s+-\s+|\s+-|-\s+|\s+|-', l1)] |
274 l += l2 | 426 if len(l2) == 1: |
275 elif len(l2) == 2 and l2[1] > l2[0]: | 427 l += l2 |
276 l += [i for i in range(l2[0], l2[1]+1)] | 428 elif len(l2) == 2 and l2[1] > l2[0]: |
277 else: | 429 l += [i for i in range(l2[0], l2[1]+1)] |
278 raise ValueError | 430 else: |
279 except (ValueError, TypeError, SyntaxError, MemoryError, RecursionError): | 431 raise ValueError |
280 return None | 432 except (ValueError, TypeError, SyntaxError, MemoryError, RecursionError): |
281 return sorted(set(l)) | 433 return(None) |
434 else: | |
435 l = [literal_eval(x) for x in ll] | |
436 if remove_duplicates: | |
437 l = list(dict.fromkeys(l)) | |
438 if sort: | |
439 l = sorted(l) | |
440 return(l) | |
282 | 441 |
283 def get_trailing_int(string): | 442 def get_trailing_int(string): |
284 indexRegex = re.compile(r'\d+$') | 443 indexRegex = re.compile(r'\d+$') |
285 mo = indexRegex.search(string) | 444 mo = indexRegex.search(string) |
286 if mo is None: | 445 if mo is None: |
287 return None | 446 return(None) |
288 else: | 447 else: |
289 return int(mo.group()) | 448 return(int(mo.group())) |
290 | 449 |
291 def input_int(s=None, v_min=None, v_max=None, default=None, inset=None): | 450 def input_int(s=None, ge=None, gt=None, le=None, lt=None, default=None, inset=None, |
451 raise_error=False, log=True): | |
452 return(_input_int_or_num('int', s, ge, gt, le, lt, default, inset, raise_error, log)) | |
453 | |
454 def input_num(s=None, ge=None, gt=None, le=None, lt=None, default=None, raise_error=False, | |
455 log=True): | |
456 return(_input_int_or_num('num', s, ge, gt, le, lt, default, None, raise_error,log)) | |
457 | |
458 def _input_int_or_num(type_str, s=None, ge=None, gt=None, le=None, lt=None, default=None, | |
459 inset=None, raise_error=False, log=True): | |
460 if type_str == 'int': | |
461 if not test_ge_gt_le_lt(ge, gt, le, lt, is_int, '_input_int_or_num', raise_error, log): | |
462 return(None) | |
463 elif type_str == 'num': | |
464 if not test_ge_gt_le_lt(ge, gt, le, lt, is_num, '_input_int_or_num', raise_error, log): | |
465 return(None) | |
466 else: | |
467 illegal_value(type_str, 'type_str', '_input_int_or_num', raise_error, log) | |
468 return(None) | |
292 if default is not None: | 469 if default is not None: |
293 if not isinstance(default, int): | 470 if not _is_int_or_num(default, type_str, raise_error=raise_error, log=log): |
294 illegal_value(default, 'default', 'input_int') | 471 return(None) |
295 return None | 472 if ge is not None and default < ge: |
473 illegal_combination(ge, 'ge', default, 'default', '_input_int_or_num', raise_error, | |
474 log) | |
475 return(None) | |
476 if gt is not None and default <= gt: | |
477 illegal_combination(gt, 'gt', default, 'default', '_input_int_or_num', raise_error, | |
478 log) | |
479 return(None) | |
480 if le is not None and default > le: | |
481 illegal_combination(le, 'le', default, 'default', '_input_int_or_num', raise_error, | |
482 log) | |
483 return(None) | |
484 if lt is not None and default >= lt: | |
485 illegal_combination(lt, 'lt', default, 'default', '_input_int_or_num', raise_error, | |
486 log) | |
487 return(None) | |
296 default_string = f' [{default}]' | 488 default_string = f' [{default}]' |
297 else: | 489 else: |
298 default_string = '' | 490 default_string = '' |
299 if v_min is not None: | |
300 if not isinstance(v_min, int): | |
301 illegal_value(v_min, 'v_min', 'input_int') | |
302 return None | |
303 if default is not None and default < v_min: | |
304 logging.error('Illegal v_min, default combination ({v_min}, {default})') | |
305 return None | |
306 if v_max is not None: | |
307 if not isinstance(v_max, int): | |
308 illegal_value(v_max, 'v_max', 'input_int') | |
309 return None | |
310 if v_min is not None and v_min > v_max: | |
311 logging.error(f'Illegal v_min, v_max combination ({v_min}, {v_max})') | |
312 return None | |
313 if default is not None and default > v_max: | |
314 logging.error('Illegal default, v_max combination ({default}, {v_max})') | |
315 return None | |
316 if inset is not None: | 491 if inset is not None: |
317 if (not isinstance(inset, (tuple, list)) or False in [True if isinstance(i, int) else | 492 if (not isinstance(inset, (tuple, list)) or any(True if not isinstance(i, int) else |
318 False for i in inset]): | 493 False for i in inset)): |
319 illegal_value(inset, 'inset', 'input_int') | 494 illegal_value(inset, 'inset', '_input_int_or_num', raise_error, log) |
320 return None | 495 return(None) |
321 if v_min is not None and v_max is not None: | 496 v_range = f'{range_string_ge_gt_le_lt(ge, gt, le, lt)}' |
322 v_range = f' ({v_min}, {v_max})' | 497 if len(v_range): |
323 elif v_min is not None: | 498 v_range = f' {v_range}' |
324 v_range = f' (>= {v_min})' | |
325 elif v_max is not None: | |
326 v_range = f' (<= {v_max})' | |
327 else: | |
328 v_range = '' | |
329 if s is None: | 499 if s is None: |
330 print(f'Enter an integer{v_range}{default_string}: ') | 500 if type_str == 'int': |
501 print(f'Enter an integer{v_range}{default_string}: ') | |
502 else: | |
503 print(f'Enter a number{v_range}{default_string}: ') | |
331 else: | 504 else: |
332 print(f'{s}{v_range}{default_string}: ') | 505 print(f'{s}{v_range}{default_string}: ') |
333 try: | 506 try: |
334 i = input() | 507 i = input() |
335 if isinstance(i, str) and not len(i): | 508 if isinstance(i, str) and not len(i): |
340 if inset and v not in inset: | 513 if inset and v not in inset: |
341 raise ValueError(f'{v} not part of the set {inset}') | 514 raise ValueError(f'{v} not part of the set {inset}') |
342 except (ValueError, TypeError, SyntaxError, MemoryError, RecursionError): | 515 except (ValueError, TypeError, SyntaxError, MemoryError, RecursionError): |
343 v = None | 516 v = None |
344 except: | 517 except: |
345 print('Unexpected error') | 518 if log: |
346 raise | 519 logger.error('Unexpected error') |
347 if not is_int(v, v_min, v_max): | 520 if raise_error: |
348 print('Illegal input, enter a valid integer') | 521 raise ValueError('Unexpected error') |
349 v = input_int(s, v_min, v_max, default) | 522 if not _is_int_or_num(v, type_str, ge, gt, le, lt): |
350 return v | 523 v = _input_int_or_num(type_str, s, ge, gt, le, lt, default, inset, raise_error, log) |
351 | 524 return(v) |
352 def input_num(s=None, v_min=None, v_max=None, default=None): | 525 |
353 if default is not None: | 526 def input_int_list(s=None, ge=None, le=None, split_on_dash=True, remove_duplicates=True, |
354 if not isinstance(default, (int, float)): | 527 sort=True, raise_error=False, log=True): |
355 illegal_value(default, 'default', 'input_num') | 528 """Prompt the user to input a list of interger and split the entered string on any combination |
356 return None | 529 of commas, whitespaces, or dashes (when split_on_dash is True) |
357 default_string = f' [{default}]' | 530 e.g: '1 3,5-8 , 12 ' -> [1, 3, 5, 6, 7, 8, 12] |
358 else: | 531 remove_duplicates: removes duplicates if True (may also change the order) |
359 default_string = '' | 532 sort: sort in ascending order if True |
360 if v_min is not None: | 533 return None upon an illegal input |
361 if not isinstance(v_min, (int, float)): | 534 """ |
362 illegal_value(vmin, 'vmin', 'input_num') | 535 return(_input_int_or_num_list('int', s, ge, le, split_on_dash, remove_duplicates, sort, |
363 return None | 536 raise_error, log)) |
364 if default is not None and default < v_min: | 537 |
365 logging.error('Illegal v_min, default combination ({v_min}, {default})') | 538 def input_num_list(s=None, ge=None, le=None, remove_duplicates=True, sort=True, raise_error=False, |
366 return None | 539 log=True): |
367 if v_max is not None: | 540 """Prompt the user to input a list of numbers and split the entered string on any combination |
368 if not isinstance(v_max, (int, float)): | 541 of commas or whitespaces |
369 illegal_value(vmax, 'vmax', 'input_num') | 542 e.g: '1.0, 3, 5.8, 12 ' -> [1.0, 3.0, 5.8, 12.0] |
370 return None | 543 remove_duplicates: removes duplicates if True (may also change the order) |
371 if v_min is not None and v_max < v_min: | 544 sort: sort in ascending order if True |
372 logging.error(f'Illegal v_min, v_max combination ({v_min}, {v_max})') | 545 return None upon an illegal input |
373 return None | 546 """ |
374 if default is not None and default > v_max: | 547 return(_input_int_or_num_list('num', s, ge, le, False, remove_duplicates, sort, raise_error, |
375 logging.error('Illegal default, v_max combination ({default}, {v_max})') | 548 log)) |
376 return None | 549 |
377 if v_min is not None and v_max is not None: | 550 def _input_int_or_num_list(type_str, s=None, ge=None, le=None, split_on_dash=True, |
378 v_range = f' ({v_min}, {v_max})' | 551 remove_duplicates=True, sort=True, raise_error=False, log=True): |
379 elif v_min is not None: | 552 #FIX do we want a limit on max dimension? |
380 v_range = f' (>= {v_min})' | 553 if type_str == 'int': |
381 elif v_max is not None: | 554 if not test_ge_gt_le_lt(ge, None, le, None, is_int, 'input_int_or_num_list', raise_error, |
382 v_range = f' (<= {v_max})' | 555 log): |
383 else: | 556 return(None) |
384 v_range = '' | 557 elif type_str == 'num': |
385 if s is None: | 558 if not test_ge_gt_le_lt(ge, None, le, None, is_num, 'input_int_or_num_list', raise_error, |
386 print(f'Enter a number{v_range}{default_string}: ') | 559 log): |
387 else: | 560 return(None) |
388 print(f'{s}{v_range}{default_string}: ') | 561 else: |
389 try: | 562 illegal_value(type_str, 'type_str', '_input_int_or_num_list') |
390 i = input() | 563 return(None) |
391 if isinstance(i, str) and not len(i): | 564 v_range = f'{range_string_ge_gt_le_lt(ge=ge, le=le)}' |
392 v = default | 565 if len(v_range): |
393 print(f'{v}') | 566 v_range = f' (each value in {v_range})' |
394 else: | |
395 v = literal_eval(i) | |
396 except (ValueError, TypeError, SyntaxError, MemoryError, RecursionError): | |
397 v = None | |
398 except: | |
399 print('Unexpected error') | |
400 raise | |
401 if not is_num(v, v_min, v_max): | |
402 print('Illegal input, enter a valid number') | |
403 v = input_num(s, v_min, v_max, default) | |
404 return v | |
405 | |
406 def input_int_list(s=None, v_min=None, v_max=None): | |
407 if v_min is not None and not isinstance(v_min, int): | |
408 illegal_value(vmin, 'vmin', 'input_int_list') | |
409 return None | |
410 if v_max is not None: | |
411 if not isinstance(v_max, int): | |
412 illegal_value(vmax, 'vmax', 'input_int_list') | |
413 return None | |
414 if v_max < v_min: | |
415 logging.error(f'Illegal v_min, v_max combination ({v_min}, {v_max})') | |
416 return None | |
417 if v_min is not None and v_max is not None: | |
418 v_range = f' (each value in ({v_min}, {v_max}))' | |
419 elif v_min is not None: | |
420 v_range = f' (each value >= {v_min})' | |
421 elif v_max is not None: | |
422 v_range = f' (each value <= {v_max})' | |
423 else: | |
424 v_range = '' | |
425 if s is None: | 567 if s is None: |
426 print(f'Enter a series of integers{v_range}: ') | 568 print(f'Enter a series of integers{v_range}: ') |
427 else: | 569 else: |
428 print(f'{s}{v_range}: ') | 570 print(f'{s}{v_range}: ') |
429 try: | 571 try: |
430 l = string_to_list(input()) | 572 l = string_to_list(input(), split_on_dash, remove_duplicates, sort) |
431 except (ValueError, TypeError, SyntaxError, MemoryError, RecursionError): | 573 except (ValueError, TypeError, SyntaxError, MemoryError, RecursionError): |
432 l = None | 574 l = None |
433 except: | 575 except: |
434 print('Unexpected error') | 576 print('Unexpected error') |
435 raise | 577 raise |
436 if (not isinstance(l, list) or | 578 if (not isinstance(l, list) or |
437 True in [True if not is_int(v, v_min, v_max) else False for v in l]): | 579 any(True if not _is_int_or_num(v, type_str, ge=ge, le=le) else False for v in l)): |
438 print('Illegal input: enter a valid set of dash/comma/whitespace separated integers '+ | 580 if split_on_dash: |
439 'e.g. 2,3,5-8,10') | 581 print('Invalid input: enter a valid set of dash/comma/whitespace separated integers '+ |
440 l = input_int_list(s, v_min, v_max) | 582 'e.g. 1 3,5-8 , 12') |
441 return l | 583 else: |
584 print('Invalid input: enter a valid set of comma/whitespace separated integers '+ | |
585 'e.g. 1 3,5 8 , 12') | |
586 l = _input_int_or_num_list(type_str, s, ge, le, split_on_dash, remove_duplicates, sort, | |
587 raise_error, log) | |
588 return(l) | |
442 | 589 |
443 def input_yesno(s=None, default=None): | 590 def input_yesno(s=None, default=None): |
444 if default is not None: | 591 if default is not None: |
445 if not isinstance(default, str): | 592 if not isinstance(default, str): |
446 illegal_value(default, 'default', 'input_yesno') | 593 illegal_value(default, 'default', 'input_yesno') |
447 return None | 594 return(None) |
448 if default.lower() in 'yes': | 595 if default.lower() in 'yes': |
449 default = 'y' | 596 default = 'y' |
450 elif default.lower() in 'no': | 597 elif default.lower() in 'no': |
451 default = 'n' | 598 default = 'n' |
452 else: | 599 else: |
453 illegal_value(default, 'default', 'input_yesno') | 600 illegal_value(default, 'default', 'input_yesno') |
454 return None | 601 return(None) |
455 default_string = f' [{default}]' | 602 default_string = f' [{default}]' |
456 else: | 603 else: |
457 default_string = '' | 604 default_string = '' |
458 if s is None: | 605 if s is None: |
459 print(f'Enter yes or no{default_string}: ') | 606 print(f'Enter yes or no{default_string}: ') |
466 if i is not None and i.lower() in 'yes': | 613 if i is not None and i.lower() in 'yes': |
467 v = True | 614 v = True |
468 elif i is not None and i.lower() in 'no': | 615 elif i is not None and i.lower() in 'no': |
469 v = False | 616 v = False |
470 else: | 617 else: |
471 print('Illegal input, enter yes or no') | 618 print('Invalid input, enter yes or no') |
472 v = input_yesno(s, default) | 619 v = input_yesno(s, default) |
473 return v | 620 return(v) |
474 | 621 |
475 def input_menu(items, default=None, header=None): | 622 def input_menu(items, default=None, header=None): |
476 if not isinstance(items, (tuple, list)) or False in [True if isinstance(i, str) else False | 623 if not isinstance(items, (tuple, list)) or any(True if not isinstance(i, str) else False |
477 for i in items]: | 624 for i in items): |
478 illegal_value(items, 'items', 'input_menu') | 625 illegal_value(items, 'items', 'input_menu') |
479 return None | 626 return(None) |
480 if default is not None: | 627 if default is not None: |
481 if not (isinstance(default, str) and default in items): | 628 if not (isinstance(default, str) and default in items): |
482 logging.error(f'Illegal value for default ({default}), must be in {items}') | 629 logger.error(f'Invalid value for default ({default}), must be in {items}') |
483 return None | 630 return(None) |
484 default_string = f' [{items.index(default)+1}]' | 631 default_string = f' [{items.index(default)+1}]' |
485 else: | 632 else: |
486 default_string = '' | 633 default_string = '' |
487 if header is None: | 634 if header is None: |
488 print(f'Choose one of the following items (1, {len(items)}){default_string}:') | 635 print(f'Choose one of the following items (1, {len(items)}){default_string}:') |
505 choice = None | 652 choice = None |
506 except: | 653 except: |
507 print('Unexpected error') | 654 print('Unexpected error') |
508 raise | 655 raise |
509 if choice is None: | 656 if choice is None: |
510 print(f'Illegal choice, enter a number between 1 and {len(items)}') | 657 print(f'Invalid choice, enter a number between 1 and {len(items)}') |
511 choice = input_menu(items, default) | 658 choice = input_menu(items, default) |
512 return choice | 659 return(choice) |
513 | 660 |
514 def create_mask(x, bounds=None, reverse_mask=False, current_mask=None): | 661 def assert_no_duplicates_in_list_of_dicts(l: list, raise_error=False) -> list: |
662 if not isinstance(l, list): | |
663 illegal_value(l, 'l', 'assert_no_duplicates_in_list_of_dicts', raise_error) | |
664 return(None) | |
665 if any(True if not isinstance(d, dict) else False for d in l): | |
666 illegal_value(l, 'l', 'assert_no_duplicates_in_list_of_dicts', raise_error) | |
667 return(None) | |
668 if len(l) != len([dict(t) for t in {tuple(sorted(d.items())) for d in l}]): | |
669 if raise_error: | |
670 raise ValueError(f'Duplicate items found in {l}') | |
671 else: | |
672 logger.error(f'Duplicate items found in {l}') | |
673 return(None) | |
674 else: | |
675 return(l) | |
676 | |
677 def assert_no_duplicate_key_in_list_of_dicts(l: list, key: str, raise_error=False) -> list: | |
678 if not isinstance(key, str): | |
679 illegal_value(key, 'key', 'assert_no_duplicate_key_in_list_of_dicts', raise_error) | |
680 return(None) | |
681 if not isinstance(l, list): | |
682 illegal_value(l, 'l', 'assert_no_duplicate_key_in_list_of_dicts', raise_error) | |
683 return(None) | |
684 if any(True if not isinstance(d, dict) else False for d in l): | |
685 illegal_value(l, 'l', 'assert_no_duplicates_in_list_of_dicts', raise_error) | |
686 return(None) | |
687 keys = [d.get(key, None) for d in l] | |
688 if None in keys or len(set(keys)) != len(l): | |
689 if raise_error: | |
690 raise ValueError(f'Duplicate or missing key ({key}) found in {l}') | |
691 else: | |
692 logger.error(f'Duplicate or missing key ({key}) found in {l}') | |
693 return(None) | |
694 else: | |
695 return(l) | |
696 | |
697 def assert_no_duplicate_attr_in_list_of_objs(l: list, attr: str, raise_error=False) -> list: | |
698 if not isinstance(attr, str): | |
699 illegal_value(attr, 'attr', 'assert_no_duplicate_attr_in_list_of_objs', raise_error) | |
700 return(None) | |
701 if not isinstance(l, list): | |
702 illegal_value(l, 'l', 'assert_no_duplicate_key_in_list_of_objs', raise_error) | |
703 return(None) | |
704 attrs = [getattr(obj, attr, None) for obj in l] | |
705 if None in attrs or len(set(attrs)) != len(l): | |
706 if raise_error: | |
707 raise ValueError(f'Duplicate or missing attr ({attr}) found in {l}') | |
708 else: | |
709 logger.error(f'Duplicate or missing attr ({attr}) found in {l}') | |
710 return(None) | |
711 else: | |
712 return(l) | |
713 | |
714 def file_exists_and_readable(path): | |
715 if not os.path.isfile(path): | |
716 raise ValueError(f'{path} is not a valid file') | |
717 elif not os.access(path, os.R_OK): | |
718 raise ValueError(f'{path} is not accessible for reading') | |
719 else: | |
720 return(path) | |
721 | |
722 def create_mask(x, bounds=None, exclude_bounds=False, current_mask=None): | |
515 # bounds is a pair of number in the same units a x | 723 # bounds is a pair of number in the same units a x |
516 if not isinstance(x, (tuple, list, np.ndarray)) or not len(x): | 724 if not isinstance(x, (tuple, list, np.ndarray)) or not len(x): |
517 logging.warning(f'Illegal input array ({x}, {type(x)})') | 725 logger.warning(f'Invalid input array ({x}, {type(x)})') |
518 return None | 726 return(None) |
519 if bounds is not None and not is_num_pair(bounds): | 727 if bounds is not None and not is_num_pair(bounds): |
520 logging.warning(f'Illegal bounds parameter ({bounds} {type(bounds)}, input ignored') | 728 logger.warning(f'Invalid bounds parameter ({bounds} {type(bounds)}, input ignored') |
521 bounds = None | 729 bounds = None |
522 if bounds is not None: | 730 if bounds is not None: |
523 if not reverse_mask: | 731 if exclude_bounds: |
732 mask = np.logical_or(x < min(bounds), x > max(bounds)) | |
733 else: | |
524 mask = np.logical_and(x > min(bounds), x < max(bounds)) | 734 mask = np.logical_and(x > min(bounds), x < max(bounds)) |
525 else: | |
526 mask = np.logical_or(x < min(bounds), x > max(bounds)) | |
527 else: | 735 else: |
528 mask = np.ones(len(x), dtype=bool) | 736 mask = np.ones(len(x), dtype=bool) |
529 if current_mask is not None: | 737 if current_mask is not None: |
530 if not isinstance(current_mask, (tuple, list, np.ndarray)) or len(current_mask) != len(x): | 738 if not isinstance(current_mask, (tuple, list, np.ndarray)) or len(current_mask) != len(x): |
531 logging.warning(f'Illegal current_mask ({current_mask}, {type(current_mask)}), '+ | 739 logger.warning(f'Invalid current_mask ({current_mask}, {type(current_mask)}), '+ |
532 'input ignored') | 740 'input ignored') |
533 else: | 741 else: |
534 mask = np.logical_and(mask, current_mask) | 742 mask = np.logical_or(mask, current_mask) |
535 if not True in mask: | 743 if not True in mask: |
536 logging.warning('Entire data array is masked') | 744 logger.warning('Entire data array is masked') |
537 return mask | 745 return(mask) |
746 | |
747 def eval_expr(name, expr, expr_variables, user_variables=None, max_depth=10, raise_error=False, | |
748 log=True, **kwargs): | |
749 """Evaluate an expression of expressions | |
750 """ | |
751 if not isinstance(name, str): | |
752 illegal_value(name, 'name', 'eval_expr', raise_error, log) | |
753 return(None) | |
754 if not isinstance(expr, str): | |
755 illegal_value(expr, 'expr', 'eval_expr', raise_error, log) | |
756 return(None) | |
757 if not is_dict_strings(expr_variables, log=False): | |
758 illegal_value(expr_variables, 'expr_variables', 'eval_expr', raise_error, log) | |
759 return(None) | |
760 if user_variables is not None and not is_dict_nums(user_variables, log=False): | |
761 illegal_value(user_variables, 'user_variables', 'eval_expr', raise_error, log) | |
762 return(None) | |
763 if not is_int(max_depth, gt=1, log=False): | |
764 illegal_value(max_depth, 'max_depth', 'eval_expr', raise_error, log) | |
765 return(None) | |
766 if not isinstance(raise_error, bool): | |
767 illegal_value(raise_error, 'raise_error', 'eval_expr', raise_error, log) | |
768 return(None) | |
769 if not isinstance(log, bool): | |
770 illegal_value(log, 'log', 'eval_expr', raise_error, log) | |
771 return(None) | |
772 # print(f'\nEvaluate the full expression for {expr}') | |
773 if 'chain' in kwargs: | |
774 chain = kwargs.pop('chain') | |
775 if not is_str_series(chain): | |
776 illegal_value(chain, 'chain', 'eval_expr', raise_error, log) | |
777 return(None) | |
778 else: | |
779 chain = [] | |
780 if len(chain) > max_depth: | |
781 error_msg = 'Exceeded maximum depth ({max_depth}) in eval_expr' | |
782 if log: | |
783 logger.error(error_msg) | |
784 if raise_error: | |
785 raise ValueError(error_msg) | |
786 return(None) | |
787 if name not in chain: | |
788 chain.append(name) | |
789 # print(f'start: chain = {chain}') | |
790 if 'ast' in kwargs: | |
791 ast = kwargs.pop('ast') | |
792 else: | |
793 ast = Interpreter() | |
794 if user_variables is not None: | |
795 ast.symtable.update(user_variables) | |
796 chain_vars = [var for var in get_ast_names(ast.parse(expr)) | |
797 if var in expr_variables and var not in ast.symtable] | |
798 # print(f'chain_vars: {chain_vars}') | |
799 save_chain = chain.copy() | |
800 for var in chain_vars: | |
801 # print(f'\n\tname = {name}, var = {var}:\n\t\t{expr_variables[var]}') | |
802 # print(f'\tchain = {chain}') | |
803 if var in chain: | |
804 error_msg = f'Circular variable {var} in eval_expr' | |
805 if log: | |
806 logger.error(error_msg) | |
807 if raise_error: | |
808 raise ValueError(error_msg) | |
809 return(None) | |
810 # print(f'\tknown symbols:\n\t\t{ast.user_defined_symbols()}\n') | |
811 if var in ast.user_defined_symbols(): | |
812 val = ast.symtable[var] | |
813 else: | |
814 #val = eval_expr(var, expr_variables[var], expr_variables, user_variables=user_variables, | |
815 val = eval_expr(var, expr_variables[var], expr_variables, max_depth=max_depth, | |
816 raise_error=raise_error, log=log, chain=chain, ast=ast) | |
817 if val is None: | |
818 return(None) | |
819 ast.symtable[var] = val | |
820 # print(f'\tval = {val}') | |
821 # print(f'\t{var} = {ast.symtable[var]}') | |
822 chain = save_chain.copy() | |
823 # print(f'\treset loop for {var}: chain = {chain}') | |
824 val = ast.eval(expr) | |
825 # print(f'return val for {expr} = {val}\n') | |
826 return(val) | |
827 | |
828 def full_gradient(expr, x, expr_name=None, expr_variables=None, valid_variables=None, max_depth=10, | |
829 raise_error=False, log=True, **kwargs): | |
830 """Compute the full gradient dexpr/dx | |
831 """ | |
832 if not isinstance(x, str): | |
833 illegal_value(x, 'x', 'full_gradient', raise_error, log) | |
834 return(None) | |
835 if expr_name is not None and not isinstance(expr_name, str): | |
836 illegal_value(expr_name, 'expr_name', 'eval_expr', raise_error, log) | |
837 return(None) | |
838 if expr_variables is not None and not is_dict_strings(expr_variables, log=False): | |
839 illegal_value(expr_variables, 'expr_variables', 'full_gradient', raise_error, log) | |
840 return(None) | |
841 if valid_variables is not None and not is_str_series(valid_variables, log=False): | |
842 illegal_value(valid_variables, 'valid_variables', 'full_gradient', raise_error, log) | |
843 if not is_int(max_depth, gt=1, log=False): | |
844 illegal_value(max_depth, 'max_depth', 'eval_expr', raise_error, log) | |
845 return(None) | |
846 if not isinstance(raise_error, bool): | |
847 illegal_value(raise_error, 'raise_error', 'eval_expr', raise_error, log) | |
848 return(None) | |
849 if not isinstance(log, bool): | |
850 illegal_value(log, 'log', 'eval_expr', raise_error, log) | |
851 return(None) | |
852 # print(f'\nGet full gradient of {expr_name} = {expr} with respect to {x}') | |
853 if expr_name is not None and expr_name == x: | |
854 return(1.0) | |
855 if 'chain' in kwargs: | |
856 chain = kwargs.pop('chain') | |
857 if not is_str_series(chain): | |
858 illegal_value(chain, 'chain', 'eval_expr', raise_error, log) | |
859 return(None) | |
860 else: | |
861 chain = [] | |
862 if len(chain) > max_depth: | |
863 error_msg = 'Exceeded maximum depth ({max_depth}) in eval_expr' | |
864 if log: | |
865 logger.error(error_msg) | |
866 if raise_error: | |
867 raise ValueError(error_msg) | |
868 return(None) | |
869 if expr_name is not None and expr_name not in chain: | |
870 chain.append(expr_name) | |
871 # print(f'start ({x}): chain = {chain}') | |
872 ast = Interpreter() | |
873 if expr_variables is None: | |
874 chain_vars = [] | |
875 else: | |
876 chain_vars = [var for var in get_ast_names(ast.parse(f'{expr}')) | |
877 if var in expr_variables and var != x and var not in ast.symtable] | |
878 # print(f'chain_vars: {chain_vars}') | |
879 if valid_variables is not None: | |
880 unknown_vars = [var for var in chain_vars if var not in valid_variables] | |
881 if len(unknown_vars): | |
882 error_msg = f'Unknown variable {unknown_vars} in {expr}' | |
883 if log: | |
884 logger.error(error_msg) | |
885 if raise_error: | |
886 raise ValueError(error_msg) | |
887 return(None) | |
888 dexpr_dx = diff(expr, x) | |
889 # print(f'direct gradient: d({expr})/d({x}) = {dexpr_dx} ({type(dexpr_dx)})') | |
890 save_chain = chain.copy() | |
891 for var in chain_vars: | |
892 # print(f'\n\texpr_name = {expr_name}, var = {var}:\n\t\t{expr}') | |
893 # print(f'\tchain = {chain}') | |
894 if var in chain: | |
895 error_msg = f'Circular variable {var} in full_gradient' | |
896 if log: | |
897 logger.error(error_msg) | |
898 if raise_error: | |
899 raise ValueError(error_msg) | |
900 return(None) | |
901 dexpr_dvar = diff(expr, var) | |
902 # print(f'\td({expr})/d({var}) = {dexpr_dvar}') | |
903 if dexpr_dvar: | |
904 dvar_dx = full_gradient(expr_variables[var], x, expr_name=var, | |
905 expr_variables=expr_variables, valid_variables=valid_variables, | |
906 max_depth=max_depth, raise_error=raise_error, log=log, chain=chain) | |
907 # print(f'\t\td({var})/d({x}) = {dvar_dx}') | |
908 if dvar_dx: | |
909 dexpr_dx = f'{dexpr_dx}+({dexpr_dvar})*({dvar_dx})' | |
910 # print(f'\t\t2: chain = {chain}') | |
911 chain = save_chain.copy() | |
912 # print(f'\treset loop for {var}: chain = {chain}') | |
913 # print(f'full gradient: d({expr})/d({x}) = {dexpr_dx} ({type(dexpr_dx)})') | |
914 # print(f'reset end: chain = {chain}\n\n') | |
915 return(simplify(dexpr_dx)) | |
916 | |
917 def bounds_from_mask(mask, return_include_bounds:bool=True): | |
918 bounds = [] | |
919 for i, m in enumerate(mask): | |
920 if m == return_include_bounds: | |
921 if len(bounds) == 0 or type(bounds[-1]) == tuple: | |
922 bounds.append(i) | |
923 else: | |
924 if len(bounds) > 0 and isinstance(bounds[-1], int): | |
925 bounds[-1] = (bounds[-1], i-1) | |
926 if len(bounds) > 0 and isinstance(bounds[-1], int): | |
927 bounds[-1] = (bounds[-1], mask.size-1) | |
928 return(bounds) | |
538 | 929 |
539 def draw_mask_1d(ydata, xdata=None, current_index_ranges=None, current_mask=None, | 930 def draw_mask_1d(ydata, xdata=None, current_index_ranges=None, current_mask=None, |
540 select_mask=True, num_index_ranges_max=None, title=None, legend=None, test_mode=False): | 931 select_mask=True, num_index_ranges_max=None, title=None, legend=None, test_mode=False): |
541 def draw_selections(ax): | 932 #FIX make color blind friendly |
933 def draw_selections(ax, current_include, current_exclude, selected_index_ranges): | |
542 ax.clear() | 934 ax.clear() |
543 ax.set_title(title) | 935 ax.set_title(title) |
544 ax.legend([legend]) | 936 ax.legend([legend]) |
545 ax.plot(xdata, ydata, 'k') | 937 ax.plot(xdata, ydata, 'k') |
546 for (low, upp) in current_include: | 938 for (low, upp) in current_include: |
568 event.xdata = index_nearest_low(xdata, event.xdata) | 960 event.xdata = index_nearest_low(xdata, event.xdata) |
569 if selected_index_ranges[-1] <= event.xdata: | 961 if selected_index_ranges[-1] <= event.xdata: |
570 selected_index_ranges[-1] = (selected_index_ranges[-1], event.xdata) | 962 selected_index_ranges[-1] = (selected_index_ranges[-1], event.xdata) |
571 else: | 963 else: |
572 selected_index_ranges[-1] = (event.xdata, selected_index_ranges[-1]) | 964 selected_index_ranges[-1] = (event.xdata, selected_index_ranges[-1]) |
573 draw_selections(event.inaxes) | 965 draw_selections(event.inaxes, current_include, current_exclude, selected_index_ranges) |
574 else: | 966 else: |
575 selected_index_ranges.pop(-1) | 967 selected_index_ranges.pop(-1) |
576 | 968 |
577 def confirm_selection(event): | 969 def confirm_selection(event): |
578 plt.close() | 970 plt.close() |
579 | 971 |
580 def clear_last_selection(event): | 972 def clear_last_selection(event): |
581 if len(selected_index_ranges): | 973 if len(selected_index_ranges): |
582 selected_index_ranges.pop(-1) | 974 selected_index_ranges.pop(-1) |
583 draw_selections(ax) | 975 else: |
584 | 976 while len(current_include): |
585 def update_mask(mask): | 977 current_include.pop() |
978 while len(current_exclude): | |
979 current_exclude.pop() | |
980 selected_mask.fill(False) | |
981 draw_selections(ax, current_include, current_exclude, selected_index_ranges) | |
982 | |
983 def update_mask(mask, selected_index_ranges, unselected_index_ranges): | |
586 for (low, upp) in selected_index_ranges: | 984 for (low, upp) in selected_index_ranges: |
587 selected_mask = np.logical_and(xdata >= xdata[low], xdata <= xdata[upp]) | 985 selected_mask = np.logical_and(xdata >= xdata[low], xdata <= xdata[upp]) |
588 mask = np.logical_or(mask, selected_mask) | 986 mask = np.logical_or(mask, selected_mask) |
589 for (low, upp) in unselected_index_ranges: | 987 for (low, upp) in unselected_index_ranges: |
590 unselected_mask = np.logical_and(xdata >= xdata[low], xdata <= xdata[upp]) | 988 unselected_mask = np.logical_and(xdata >= xdata[low], xdata <= xdata[upp]) |
591 mask[unselected_mask] = False | 989 mask[unselected_mask] = False |
592 return mask | 990 return(mask) |
593 | 991 |
594 def update_index_ranges(mask): | 992 def update_index_ranges(mask): |
595 # Update the currently included index ranges (where mask is True) | 993 # Update the currently included index ranges (where mask is True) |
596 current_include = [] | 994 current_include = [] |
597 for i, m in enumerate(mask): | 995 for i, m in enumerate(mask): |
601 else: | 999 else: |
602 if len(current_include) > 0 and isinstance(current_include[-1], int): | 1000 if len(current_include) > 0 and isinstance(current_include[-1], int): |
603 current_include[-1] = (current_include[-1], i-1) | 1001 current_include[-1] = (current_include[-1], i-1) |
604 if len(current_include) > 0 and isinstance(current_include[-1], int): | 1002 if len(current_include) > 0 and isinstance(current_include[-1], int): |
605 current_include[-1] = (current_include[-1], num_data-1) | 1003 current_include[-1] = (current_include[-1], num_data-1) |
606 return current_include | 1004 return(current_include) |
607 | 1005 |
608 # Check for valid inputs | 1006 # Check inputs |
609 ydata = np.asarray(ydata) | 1007 ydata = np.asarray(ydata) |
610 if ydata.ndim > 1: | 1008 if ydata.ndim > 1: |
611 logging.warning(f'Illegal ydata dimension ({ydata.ndim})') | 1009 logger.warning(f'Invalid ydata dimension ({ydata.ndim})') |
612 return None, None | 1010 return(None, None) |
613 num_data = ydata.size | 1011 num_data = ydata.size |
614 if xdata is None: | 1012 if xdata is None: |
615 xdata = np.arange(num_data) | 1013 xdata = np.arange(num_data) |
616 else: | 1014 else: |
617 xdata = np.asarray(xdata, dtype=np.float64) | 1015 xdata = np.asarray(xdata, dtype=np.float64) |
618 if xdata.ndim > 1 or xdata.size != num_data: | 1016 if xdata.ndim > 1 or xdata.size != num_data: |
619 logging.warning(f'Illegal xdata shape ({xdata.shape})') | 1017 logger.warning(f'Invalid xdata shape ({xdata.shape})') |
620 return None, None | 1018 return(None, None) |
621 if not np.all(xdata[:-1] < xdata[1:]): | 1019 if not np.all(xdata[:-1] < xdata[1:]): |
622 logging.warning('Illegal xdata: must be monotonically increasing') | 1020 logger.warning('Invalid xdata: must be monotonically increasing') |
623 return None, None | 1021 return(None, None) |
624 if current_index_ranges is not None: | 1022 if current_index_ranges is not None: |
625 if not isinstance(current_index_ranges, (tuple, list)): | 1023 if not isinstance(current_index_ranges, (tuple, list)): |
626 logging.warning('Illegal current_index_ranges parameter ({current_index_ranges}, '+ | 1024 logger.warning('Invalid current_index_ranges parameter ({current_index_ranges}, '+ |
627 f'{type(current_index_ranges)})') | 1025 f'{type(current_index_ranges)})') |
628 return None, None | 1026 return(None, None) |
629 if not isinstance(select_mask, bool): | 1027 if not isinstance(select_mask, bool): |
630 logging.warning('Illegal select_mask parameter ({select_mask}, {type(select_mask)})') | 1028 logger.warning('Invalid select_mask parameter ({select_mask}, {type(select_mask)})') |
631 return None, None | 1029 return(None, None) |
632 if num_index_ranges_max is not None: | 1030 if num_index_ranges_max is not None: |
633 logging.warning('num_index_ranges_max input not yet implemented in draw_mask_1d') | 1031 logger.warning('num_index_ranges_max input not yet implemented in draw_mask_1d') |
634 if title is None: | 1032 if title is None: |
635 title = 'select ranges of data' | 1033 title = 'select ranges of data' |
636 elif not isinstance(title, str): | 1034 elif not isinstance(title, str): |
637 illegal(title, 'title') | 1035 illegal(title, 'title') |
638 title = '' | 1036 title = '' |
666 if low < 0: | 1064 if low < 0: |
667 low = 0 | 1065 low = 0 |
668 if upp >= num_data: | 1066 if upp >= num_data: |
669 upp = num_data-1 | 1067 upp = num_data-1 |
670 selected_index_ranges.append((low, upp)) | 1068 selected_index_ranges.append((low, upp)) |
671 selected_mask = update_mask(selected_mask) | 1069 selected_mask = update_mask(selected_mask, selected_index_ranges, unselected_index_ranges) |
672 if current_index_ranges is not None and current_mask is not None: | 1070 if current_index_ranges is not None and current_mask is not None: |
673 selected_mask = np.logical_and(current_mask, selected_mask) | 1071 selected_mask = np.logical_and(current_mask, selected_mask) |
674 if current_mask is not None: | 1072 if current_mask is not None: |
675 selected_index_ranges = update_index_ranges(selected_mask) | 1073 selected_index_ranges = update_index_ranges(selected_mask) |
676 | 1074 |
695 | 1093 |
696 # Set up matplotlib figure | 1094 # Set up matplotlib figure |
697 plt.close('all') | 1095 plt.close('all') |
698 fig, ax = plt.subplots() | 1096 fig, ax = plt.subplots() |
699 plt.subplots_adjust(bottom=0.2) | 1097 plt.subplots_adjust(bottom=0.2) |
700 draw_selections(ax) | 1098 draw_selections(ax, current_include, current_exclude, selected_index_ranges) |
701 | 1099 |
702 # Set up event handling for click-and-drag range selection | 1100 # Set up event handling for click-and-drag range selection |
703 cid_click = fig.canvas.mpl_connect('button_press_event', onclick) | 1101 cid_click = fig.canvas.mpl_connect('button_press_event', onclick) |
704 cid_release = fig.canvas.mpl_connect('button_release_event', onrelease) | 1102 cid_release = fig.canvas.mpl_connect('button_release_event', onrelease) |
705 | 1103 |
722 if not select_mask: | 1120 if not select_mask: |
723 selected_index_ranges, unselected_index_ranges = unselected_index_ranges, \ | 1121 selected_index_ranges, unselected_index_ranges = unselected_index_ranges, \ |
724 selected_index_ranges | 1122 selected_index_ranges |
725 | 1123 |
726 # Update the mask with the currently selected/unselected x-ranges | 1124 # Update the mask with the currently selected/unselected x-ranges |
727 selected_mask = update_mask(selected_mask) | 1125 selected_mask = update_mask(selected_mask, selected_index_ranges, unselected_index_ranges) |
728 | 1126 |
729 # Update the currently included index ranges (where mask is True) | 1127 # Update the currently included index ranges (where mask is True) |
730 current_include = update_index_ranges(selected_mask) | 1128 current_include = update_index_ranges(selected_mask) |
731 | 1129 |
732 return selected_mask, current_include | 1130 return(selected_mask, current_include) |
733 | 1131 |
734 def findImageFiles(path, filetype, name=None): | 1132 def select_peaks(ydata:np.ndarray, x_values:np.ndarray=None, x_mask:np.ndarray=None, |
1133 peak_x_values:np.ndarray=np.array([]), peak_x_indices:np.ndarray=np.array([]), | |
1134 return_peak_x_values:bool=False, return_peak_x_indices:bool=False, | |
1135 return_peak_input_indices:bool=False, return_sorted:bool=False, | |
1136 title:str=None, xlabel:str=None, ylabel:str=None) -> list : | |
1137 | |
1138 # Check arguments | |
1139 if (len(peak_x_values) > 0 or return_peak_x_values) and not len(x_values) > 0: | |
1140 raise RuntimeError('Cannot use peak_x_values or return_peak_x_values without x_values') | |
1141 if not ((len(peak_x_values) > 0) ^ (len(peak_x_indices) > 0)): | |
1142 raise RuntimeError('Use exactly one of peak_x_values or peak_x_indices') | |
1143 return_format_iter = iter((return_peak_x_values, return_peak_x_indices, return_peak_input_indices)) | |
1144 if not (any(return_format_iter) and not any(return_format_iter)): | |
1145 raise RuntimeError('Exactly one of return_peak_x_values, return_peak_x_indices, or '+ | |
1146 'return_peak_input_indices must be True') | |
1147 | |
1148 EXCLUDE_PEAK_PROPERTIES = {'color': 'black', 'linestyle': '--','linewidth': 1, | |
1149 'marker': 10, 'markersize': 5, 'fillstyle': 'none'} | |
1150 INCLUDE_PEAK_PROPERTIES = {'color': 'green', 'linestyle': '-', 'linewidth': 2, | |
1151 'marker': 10, 'markersize': 10, 'fillstyle': 'full'} | |
1152 MASKED_PEAK_PROPERTIES = {'color': 'gray', 'linestyle': ':', 'linewidth': 1} | |
1153 | |
1154 # Setup reference data & plot | |
1155 x_indices = np.arange(len(ydata)) | |
1156 if x_values is None: | |
1157 x_values = x_indices | |
1158 if x_mask is None: | |
1159 x_mask = np.full(x_values.shape, True, dtype=bool) | |
1160 fig, ax = plt.subplots() | |
1161 handles = ax.plot(x_values, ydata, label='Reference data') | |
1162 handles.append(mlines.Line2D([], [], label='Excluded / unselected HKL', **EXCLUDE_PEAK_PROPERTIES)) | |
1163 handles.append(mlines.Line2D([], [], label='Included / selected HKL', **INCLUDE_PEAK_PROPERTIES)) | |
1164 handles.append(mlines.Line2D([], [], label='HKL in masked region (unselectable)', **MASKED_PEAK_PROPERTIES)) | |
1165 ax.legend(handles=handles, loc='upper right') | |
1166 ax.set(title=title, xlabel=xlabel, ylabel=ylabel) | |
1167 | |
1168 | |
1169 # Plot vertical line at each peak | |
1170 value_to_index = lambda x_value: int(np.argmin(abs(x_values - x_value))) | |
1171 if len(peak_x_indices) > 0: | |
1172 peak_x_values = x_values[peak_x_indices] | |
1173 else: | |
1174 peak_x_indices = np.array(list(map(value_to_index, peak_x_values))) | |
1175 peak_vlines = [] | |
1176 for loc in peak_x_values: | |
1177 nearest_index = value_to_index(loc) | |
1178 if nearest_index in x_indices[x_mask]: | |
1179 peak_vline = ax.axvline(loc, **EXCLUDE_PEAK_PROPERTIES) | |
1180 peak_vline.set_picker(5) | |
1181 else: | |
1182 peak_vline = ax.axvline(loc, **MASKED_PEAK_PROPERTIES) | |
1183 peak_vlines.append(peak_vline) | |
1184 | |
1185 # Indicate masked regions by gray-ing out the axes facecolor | |
1186 mask_exclude_bounds = bounds_from_mask(x_mask, return_include_bounds=False) | |
1187 for (low, upp) in mask_exclude_bounds: | |
1188 xlow = x_values[low] | |
1189 xupp = x_values[upp] | |
1190 ax.axvspan(xlow, xupp, facecolor='gray', alpha=0.5) | |
1191 | |
1192 # Setup peak picking | |
1193 selected_peak_input_indices = [] | |
1194 def onpick(event): | |
1195 try: | |
1196 peak_index = peak_vlines.index(event.artist) | |
1197 except: | |
1198 pass | |
1199 else: | |
1200 peak_vline = event.artist | |
1201 if peak_index in selected_peak_input_indices: | |
1202 peak_vline.set(**EXCLUDE_PEAK_PROPERTIES) | |
1203 selected_peak_input_indices.remove(peak_index) | |
1204 else: | |
1205 peak_vline.set(**INCLUDE_PEAK_PROPERTIES) | |
1206 selected_peak_input_indices.append(peak_index) | |
1207 plt.draw() | |
1208 cid_pick_peak = fig.canvas.mpl_connect('pick_event', onpick) | |
1209 | |
1210 # Setup "Confirm" button | |
1211 def confirm_selection(event): | |
1212 plt.close() | |
1213 plt.subplots_adjust(bottom=0.2) | |
1214 confirm_b = Button(plt.axes([0.75, 0.05, 0.15, 0.075]), 'Confirm') | |
1215 cid_confirm = confirm_b.on_clicked(confirm_selection) | |
1216 | |
1217 # Show figure for user interaction | |
1218 plt.show() | |
1219 | |
1220 # Disconnect callbacks when figure is closed | |
1221 fig.canvas.mpl_disconnect(cid_pick_peak) | |
1222 confirm_b.disconnect(cid_confirm) | |
1223 | |
1224 if return_peak_input_indices: | |
1225 selected_peaks = np.array(selected_peak_input_indices) | |
1226 if return_peak_x_values: | |
1227 selected_peaks = peak_x_values[selected_peak_input_indices] | |
1228 if return_peak_x_indices: | |
1229 selected_peaks = peak_x_indices[selected_peak_input_indices] | |
1230 | |
1231 if return_sorted: | |
1232 selected_peaks.sort() | |
1233 | |
1234 return(selected_peaks) | |
1235 | |
1236 def find_image_files(path, filetype, name=None): | |
735 if isinstance(name, str): | 1237 if isinstance(name, str): |
736 name = f' {name} ' | 1238 name = f'{name.strip()} ' |
737 else: | 1239 else: |
738 name = ' ' | 1240 name = '' |
739 # Find available index range | 1241 # Find available index range |
740 if filetype == 'tif': | 1242 if filetype == 'tif': |
741 if not isinstance(path, str) or not os.path.isdir(path): | 1243 if not isinstance(path, str) or not os.path.isdir(path): |
742 illegal_value(path, 'path', 'findImageFiles') | 1244 illegal_value(path, 'path', 'find_image_files') |
743 return -1, 0, [] | 1245 return(-1, 0, []) |
744 indexRegex = re.compile(r'\d+') | 1246 indexRegex = re.compile(r'\d+') |
745 # At this point only tiffs | 1247 # At this point only tiffs |
746 files = sorted([f for f in os.listdir(path) if os.path.isfile(os.path.join(path, f)) and | 1248 files = sorted([f for f in os.listdir(path) if os.path.isfile(os.path.join(path, f)) and |
747 f.endswith('.tif') and indexRegex.search(f)]) | 1249 f.endswith('.tif') and indexRegex.search(f)]) |
748 num_imgs = len(files) | 1250 num_img = len(files) |
749 if num_imgs < 1: | 1251 if num_img < 1: |
750 logging.warning('No available'+name+'files') | 1252 logger.warning(f'No available {name}files') |
751 return -1, 0, [] | 1253 return(-1, 0, []) |
752 first_index = indexRegex.search(files[0]).group() | 1254 first_index = indexRegex.search(files[0]).group() |
753 last_index = indexRegex.search(files[-1]).group() | 1255 last_index = indexRegex.search(files[-1]).group() |
754 if first_index is None or last_index is None: | 1256 if first_index is None or last_index is None: |
755 logging.error('Unable to find correctly indexed'+name+'images') | 1257 logger.error(f'Unable to find correctly indexed {name}images') |
756 return -1, 0, [] | 1258 return(-1, 0, []) |
757 first_index = int(first_index) | 1259 first_index = int(first_index) |
758 last_index = int(last_index) | 1260 last_index = int(last_index) |
759 if num_imgs != last_index-first_index+1: | 1261 if num_img != last_index-first_index+1: |
760 logging.error('Non-consecutive set of indices for'+name+'images') | 1262 logger.error(f'Non-consecutive set of indices for {name}images') |
761 return -1, 0, [] | 1263 return(-1, 0, []) |
762 paths = [os.path.join(path, f) for f in files] | 1264 paths = [os.path.join(path, f) for f in files] |
763 elif filetype == 'h5': | 1265 elif filetype == 'h5': |
764 if not isinstance(path, str) or not os.path.isfile(path): | 1266 if not isinstance(path, str) or not os.path.isfile(path): |
765 illegal_value(path, 'path', 'findImageFiles') | 1267 illegal_value(path, 'path', 'find_image_files') |
766 return -1, 0, [] | 1268 return(-1, 0, []) |
767 # At this point only h5 in alamo2 detector style | 1269 # At this point only h5 in alamo2 detector style |
768 first_index = 0 | 1270 first_index = 0 |
769 with h5py.File(path, 'r') as f: | 1271 with h5py.File(path, 'r') as f: |
770 num_imgs = f['entry/instrument/detector/data'].shape[0] | 1272 num_img = f['entry/instrument/detector/data'].shape[0] |
771 last_index = num_imgs-1 | 1273 last_index = num_img-1 |
772 paths = [path] | 1274 paths = [path] |
773 else: | 1275 else: |
774 illegal_value(filetype, 'filetype', 'findImageFiles') | 1276 illegal_value(filetype, 'filetype', 'find_image_files') |
775 return -1, 0, [] | 1277 return(-1, 0, []) |
776 logging.debug('\nNumber of available'+name+f'images: {num_imgs}') | 1278 logger.info(f'Number of available {name}images: {num_img}') |
777 logging.debug('Index range of available'+name+f'images: [{first_index}, '+ | 1279 logger.info(f'Index range of available {name}images: [{first_index}, '+ |
778 f'{last_index}]') | 1280 f'{last_index}]') |
779 | 1281 |
780 return first_index, num_imgs, paths | 1282 return(first_index, num_img, paths) |
781 | 1283 |
782 def selectImageRange(first_index, offset, num_imgs, name=None, num_required=None): | 1284 def select_image_range(first_index, offset, num_available, num_img=None, name=None, |
1285 num_required=None): | |
783 if isinstance(name, str): | 1286 if isinstance(name, str): |
784 name = f' {name} ' | 1287 name = f'{name.strip()} ' |
785 else: | 1288 else: |
786 name = ' ' | 1289 name = '' |
787 # Check existing values | 1290 # Check existing values |
788 use_input = False | 1291 if not is_int(num_available, gt=0): |
789 if (is_int(first_index, 0) and is_int(offset, 0) and is_int(num_imgs, 1)): | 1292 logger.warning(f'No available {name}images') |
790 if offset < 0: | 1293 return(0, 0, 0) |
791 use_input = input_yesno(f'\nCurrent{name}first index = {first_index}, '+ | 1294 if num_img is not None and not is_int(num_img, ge=0): |
792 'use this value (y/n)?', 'y') | 1295 illegal_value(num_img, 'num_img', 'select_image_range') |
793 else: | 1296 return(0, 0, 0) |
794 use_input = input_yesno(f'\nCurrent{name}first index/offset = '+ | 1297 if is_int(first_index, ge=0) and is_int(offset, ge=0): |
795 f'{first_index}/{offset}, use these values (y/n)?', 'y') | |
796 if num_required is None: | 1298 if num_required is None: |
797 if use_input: | 1299 if input_yesno(f'\nCurrent {name}first image index/offset = {first_index}/{offset},'+ |
798 use_input = input_yesno(f'Current number of{name}images = '+ | 1300 'use these values (y/n)?', 'y'): |
799 f'{num_imgs}, use this value (y/n)? ', 'y') | 1301 if num_img is not None: |
800 if use_input: | 1302 if input_yesno(f'Current number of {name}images = {num_img}, '+ |
801 return first_index, offset, num_imgs | 1303 'use this value (y/n)? ', 'y'): |
1304 return(first_index, offset, num_img) | |
1305 else: | |
1306 if input_yesno(f'Number of available {name}images = {num_available}, '+ | |
1307 'use all (y/n)? ', 'y'): | |
1308 return(first_index, offset, num_available) | |
1309 else: | |
1310 if input_yesno(f'\nCurrent {name}first image offset = {offset}, '+ | |
1311 f'use this values (y/n)?', 'y'): | |
1312 return(first_index, offset, num_required) | |
802 | 1313 |
803 # Check range against requirements | 1314 # Check range against requirements |
804 if num_imgs < 1: | |
805 logging.warning('No available'+name+'images') | |
806 return -1, -1, 0 | |
807 if num_required is None: | 1315 if num_required is None: |
808 if num_imgs == 1: | 1316 if num_available == 1: |
809 return first_index, 0, 1 | 1317 return(first_index, 0, 1) |
810 else: | 1318 else: |
811 if not is_int(num_required, 1): | 1319 if not is_int(num_required, ge=1): |
812 illegal_value(num_required, 'num_required', 'selectImageRange') | 1320 illegal_value(num_required, 'num_required', 'select_image_range') |
813 return -1, -1, 0 | 1321 return(0, 0, 0) |
814 if num_imgs < num_required: | 1322 if num_available < num_required: |
815 logging.error('Unable to find the required'+name+ | 1323 logger.error(f'Unable to find the required {name}images ({num_available} out of '+ |
816 f'images ({num_imgs} out of {num_required})') | 1324 f'{num_required})') |
817 return -1, -1, 0 | 1325 return(0, 0, 0) |
818 | 1326 |
819 # Select index range | 1327 # Select index range |
820 print('\nThe number of available'+name+f'images is {num_imgs}') | 1328 print(f'\nThe number of available {name}images is {num_available}') |
821 if num_required is None: | 1329 if num_required is None: |
822 last_index = first_index+num_imgs | 1330 last_index = first_index+num_available |
823 use_all = f'Use all ([{first_index}, {last_index}])' | 1331 use_all = f'Use all ([{first_index}, {last_index}])' |
824 pick_offset = 'Pick a first index offset and a number of images' | 1332 pick_offset = 'Pick the first image index offset and the number of images' |
825 pick_bounds = 'Pick the first and last index' | 1333 pick_bounds = 'Pick the first and last image index' |
826 choice = input_menu([use_all, pick_offset, pick_bounds], default=pick_offset) | 1334 choice = input_menu([use_all, pick_offset, pick_bounds], default=pick_offset) |
827 if not choice: | 1335 if not choice: |
828 offset = 0 | 1336 offset = 0 |
1337 num_img = num_available | |
829 elif choice == 1: | 1338 elif choice == 1: |
830 offset = input_int('Enter the first index offset', 0, last_index-first_index) | 1339 offset = input_int('Enter the first index offset', ge=0, le=last_index-first_index) |
831 first_index += offset | 1340 if first_index+offset == last_index: |
832 if first_index == last_index: | 1341 num_img = 1 |
833 num_imgs = 1 | |
834 else: | 1342 else: |
835 num_imgs = input_int('Enter the number of images', 1, num_imgs-offset) | 1343 num_img = input_int('Enter the number of images', ge=1, le=num_available-offset) |
836 else: | 1344 else: |
837 offset = input_int('Enter the first index', first_index, last_index) | 1345 offset = input_int('Enter the first index', ge=first_index, le=last_index) |
838 first_index += offset | 1346 num_img = 1-offset+input_int('Enter the last index', ge=offset, le=last_index) |
839 num_imgs = input_int('Enter the last index', first_index, last_index)-first_index+1 | 1347 offset -= first_index |
840 else: | 1348 else: |
841 use_all = f'Use ([{first_index}, {first_index+num_required-1}])' | 1349 use_all = f'Use ([{first_index}, {first_index+num_required-1}])' |
842 pick_offset = 'Pick the first index offset' | 1350 pick_offset = 'Pick the first index offset' |
843 choice = input_menu([use_all, pick_offset], pick_offset) | 1351 choice = input_menu([use_all, pick_offset], pick_offset) |
844 offset = 0 | 1352 offset = 0 |
845 if choice == 1: | 1353 if choice == 1: |
846 offset = input_int('Enter the first index offset', 0, num_imgs-num_required) | 1354 offset = input_int('Enter the first index offset', ge=0, le=num_available-num_required) |
847 first_index += offset | 1355 num_img = num_required |
848 num_imgs = num_required | 1356 |
849 | 1357 return(first_index, offset, num_img) |
850 return first_index, offset, num_imgs | 1358 |
851 | 1359 def load_image(f, img_x_bounds=None, img_y_bounds=None): |
852 def loadImage(f, img_x_bounds=None, img_y_bounds=None): | |
853 """Load a single image from file. | 1360 """Load a single image from file. |
854 """ | 1361 """ |
855 if not os.path.isfile(f): | 1362 if not os.path.isfile(f): |
856 logging.error(f'Unable to load {f}') | 1363 logger.error(f'Unable to load {f}') |
857 return None | 1364 return(None) |
858 img_read = plt.imread(f) | 1365 img_read = plt.imread(f) |
859 if not img_x_bounds: | 1366 if not img_x_bounds: |
860 img_x_bounds = (0, img_read.shape[0]) | 1367 img_x_bounds = (0, img_read.shape[0]) |
861 else: | 1368 else: |
862 if (not isinstance(img_x_bounds, (tuple, list)) or len(img_x_bounds) != 2 or | 1369 if (not isinstance(img_x_bounds, (tuple, list)) or len(img_x_bounds) != 2 or |
863 not (0 <= img_x_bounds[0] < img_x_bounds[1] <= img_read.shape[0])): | 1370 not (0 <= img_x_bounds[0] < img_x_bounds[1] <= img_read.shape[0])): |
864 logging.error(f'inconsistent row dimension in {f}') | 1371 logger.error(f'inconsistent row dimension in {f}') |
865 return None | 1372 return(None) |
866 if not img_y_bounds: | 1373 if not img_y_bounds: |
867 img_y_bounds = (0, img_read.shape[1]) | 1374 img_y_bounds = (0, img_read.shape[1]) |
868 else: | 1375 else: |
869 if (not isinstance(img_y_bounds, list) or len(img_y_bounds) != 2 or | 1376 if (not isinstance(img_y_bounds, list) or len(img_y_bounds) != 2 or |
870 not (0 <= img_y_bounds[0] < img_y_bounds[1] <= img_read.shape[1])): | 1377 not (0 <= img_y_bounds[0] < img_y_bounds[1] <= img_read.shape[1])): |
871 logging.error(f'inconsistent column dimension in {f}') | 1378 logger.error(f'inconsistent column dimension in {f}') |
872 return None | 1379 return(None) |
873 return img_read[img_x_bounds[0]:img_x_bounds[1],img_y_bounds[0]:img_y_bounds[1]] | 1380 return(img_read[img_x_bounds[0]:img_x_bounds[1],img_y_bounds[0]:img_y_bounds[1]]) |
874 | 1381 |
875 def loadImageStack(files, filetype, img_offset, num_imgs, num_img_skip=0, | 1382 def load_image_stack(files, filetype, img_offset, num_img, num_img_skip=0, |
876 img_x_bounds=None, img_y_bounds=None): | 1383 img_x_bounds=None, img_y_bounds=None): |
877 """Load a set of images and return them as a stack. | 1384 """Load a set of images and return them as a stack. |
878 """ | 1385 """ |
879 logging.debug(f'img_offset = {img_offset}') | 1386 logger.debug(f'img_offset = {img_offset}') |
880 logging.debug(f'num_imgs = {num_imgs}') | 1387 logger.debug(f'num_img = {num_img}') |
881 logging.debug(f'num_img_skip = {num_img_skip}') | 1388 logger.debug(f'num_img_skip = {num_img_skip}') |
882 logging.debug(f'\nfiles:\n{files}\n') | 1389 logger.debug(f'\nfiles:\n{files}\n') |
883 img_stack = np.array([]) | 1390 img_stack = np.array([]) |
884 if filetype == 'tif': | 1391 if filetype == 'tif': |
885 img_read_stack = [] | 1392 img_read_stack = [] |
886 i = 1 | 1393 i = 1 |
887 t0 = time() | 1394 t0 = time() |
888 for f in files[img_offset:img_offset+num_imgs:num_img_skip+1]: | 1395 for f in files[img_offset:img_offset+num_img:num_img_skip+1]: |
889 if not i%20: | 1396 if not i%20: |
890 logging.info(f' loading {i}/{num_imgs}: {f}') | 1397 logger.info(f' loading {i}/{num_img}: {f}') |
891 else: | 1398 else: |
892 logging.debug(f' loading {i}/{num_imgs}: {f}') | 1399 logger.debug(f' loading {i}/{num_img}: {f}') |
893 img_read = loadImage(f, img_x_bounds, img_y_bounds) | 1400 img_read = load_image(f, img_x_bounds, img_y_bounds) |
894 img_read_stack.append(img_read) | 1401 img_read_stack.append(img_read) |
895 i += num_img_skip+1 | 1402 i += num_img_skip+1 |
896 img_stack = np.stack([img_read for img_read in img_read_stack]) | 1403 img_stack = np.stack([img_read for img_read in img_read_stack]) |
897 logging.info(f'... done in {time()-t0:.2f} seconds!') | 1404 logger.info(f'... done in {time()-t0:.2f} seconds!') |
898 logging.debug(f'img_stack shape = {np.shape(img_stack)}') | 1405 logger.debug(f'img_stack shape = {np.shape(img_stack)}') |
899 del img_read_stack, img_read | 1406 del img_read_stack, img_read |
900 elif filetype == 'h5': | 1407 elif filetype == 'h5': |
901 if not isinstance(files[0], str) and not os.path.isfile(files[0]): | 1408 if not isinstance(files[0], str) and not os.path.isfile(files[0]): |
902 illegal_value(files[0], 'files[0]', 'loadImageStack') | 1409 illegal_value(files[0], 'files[0]', 'load_image_stack') |
903 return img_stack | 1410 return(img_stack) |
904 t0 = time() | 1411 t0 = time() |
905 logging.info(f'Loading {files[0]}') | 1412 logger.info(f'Loading {files[0]}') |
906 with h5py.File(files[0], 'r') as f: | 1413 with h5py.File(files[0], 'r') as f: |
907 shape = f['entry/instrument/detector/data'].shape | 1414 shape = f['entry/instrument/detector/data'].shape |
908 if len(shape) != 3: | 1415 if len(shape) != 3: |
909 logging.error(f'inconsistent dimensions in {files[0]}') | 1416 logger.error(f'inconsistent dimensions in {files[0]}') |
910 if not img_x_bounds: | 1417 if not img_x_bounds: |
911 img_x_bounds = (0, shape[1]) | 1418 img_x_bounds = (0, shape[1]) |
912 else: | 1419 else: |
913 if (not isinstance(img_x_bounds, (tuple, list)) or len(img_x_bounds) != 2 or | 1420 if (not isinstance(img_x_bounds, (tuple, list)) or len(img_x_bounds) != 2 or |
914 not (0 <= img_x_bounds[0] < img_x_bounds[1] <= shape[1])): | 1421 not (0 <= img_x_bounds[0] < img_x_bounds[1] <= shape[1])): |
915 logging.error(f'inconsistent row dimension in {files[0]} {img_x_bounds} '+ | 1422 logger.error(f'inconsistent row dimension in {files[0]} {img_x_bounds} '+ |
916 f'{shape[1]}') | 1423 f'{shape[1]}') |
917 if not img_y_bounds: | 1424 if not img_y_bounds: |
918 img_y_bounds = (0, shape[2]) | 1425 img_y_bounds = (0, shape[2]) |
919 else: | 1426 else: |
920 if (not isinstance(img_y_bounds, list) or len(img_y_bounds) != 2 or | 1427 if (not isinstance(img_y_bounds, list) or len(img_y_bounds) != 2 or |
921 not (0 <= img_y_bounds[0] < img_y_bounds[1] <= shape[2])): | 1428 not (0 <= img_y_bounds[0] < img_y_bounds[1] <= shape[2])): |
922 logging.error(f'inconsistent column dimension in {files[0]}') | 1429 logger.error(f'inconsistent column dimension in {files[0]}') |
923 img_stack = f.get('entry/instrument/detector/data')[ | 1430 img_stack = f.get('entry/instrument/detector/data')[ |
924 img_offset:img_offset+num_imgs:num_img_skip+1, | 1431 img_offset:img_offset+num_img:num_img_skip+1, |
925 img_x_bounds[0]:img_x_bounds[1],img_y_bounds[0]:img_y_bounds[1]] | 1432 img_x_bounds[0]:img_x_bounds[1],img_y_bounds[0]:img_y_bounds[1]] |
926 logging.info(f'... done in {time()-t0:.2f} seconds!') | 1433 logger.info(f'... done in {time()-t0:.2f} seconds!') |
927 else: | 1434 else: |
928 illegal_value(filetype, 'filetype', 'loadImageStack') | 1435 illegal_value(filetype, 'filetype', 'load_image_stack') |
929 return img_stack | 1436 return(img_stack) |
930 | 1437 |
931 def combine_tiffs_in_h5(files, num_imgs, h5_filename): | 1438 def combine_tiffs_in_h5(files, num_img, h5_filename): |
932 img_stack = loadImageStack(files, 'tif', 0, num_imgs) | 1439 img_stack = load_image_stack(files, 'tif', 0, num_img) |
933 with h5py.File(h5_filename, 'w') as f: | 1440 with h5py.File(h5_filename, 'w') as f: |
934 f.create_dataset('entry/instrument/detector/data', data=img_stack) | 1441 f.create_dataset('entry/instrument/detector/data', data=img_stack) |
935 del img_stack | 1442 del img_stack |
936 return [h5_filename] | 1443 return([h5_filename]) |
937 | 1444 |
938 def clearImshow(title=None): | 1445 def clear_imshow(title=None): |
939 plt.ioff() | 1446 plt.ioff() |
940 if title is None: | 1447 if title is None: |
941 title = 'quick imshow' | 1448 title = 'quick imshow' |
942 elif not isinstance(title, str): | 1449 elif not isinstance(title, str): |
943 illegal_value(title, 'title', 'clearImshow') | 1450 illegal_value(title, 'title', 'clear_imshow') |
944 return | 1451 return |
945 plt.close(fig=title) | 1452 plt.close(fig=title) |
946 | 1453 |
947 def clearPlot(title=None): | 1454 def clear_plot(title=None): |
948 plt.ioff() | 1455 plt.ioff() |
949 if title is None: | 1456 if title is None: |
950 title = 'quick plot' | 1457 title = 'quick plot' |
951 elif not isinstance(title, str): | 1458 elif not isinstance(title, str): |
952 illegal_value(title, 'title', 'clearPlot') | 1459 illegal_value(title, 'title', 'clear_plot') |
953 return | 1460 return |
954 plt.close(fig=title) | 1461 plt.close(fig=title) |
955 | 1462 |
956 def quickImshow(a, title=None, path=None, name=None, save_fig=False, save_only=False, | 1463 def quick_imshow(a, title=None, path=None, name=None, save_fig=False, save_only=False, |
957 clear=True, extent=None, show_grid=False, grid_color='w', grid_linewidth=1, **kwargs): | 1464 clear=True, extent=None, show_grid=False, grid_color='w', grid_linewidth=1, |
1465 block=False, **kwargs): | |
958 if title is not None and not isinstance(title, str): | 1466 if title is not None and not isinstance(title, str): |
959 illegal_value(title, 'title', 'quickImshow') | 1467 illegal_value(title, 'title', 'quick_imshow') |
960 return | 1468 return |
961 if path is not None and not isinstance(path, str): | 1469 if path is not None and not isinstance(path, str): |
962 illegal_value(path, 'path', 'quickImshow') | 1470 illegal_value(path, 'path', 'quick_imshow') |
963 return | 1471 return |
964 if not isinstance(save_fig, bool): | 1472 if not isinstance(save_fig, bool): |
965 illegal_value(save_fig, 'save_fig', 'quickImshow') | 1473 illegal_value(save_fig, 'save_fig', 'quick_imshow') |
966 return | 1474 return |
967 if not isinstance(save_only, bool): | 1475 if not isinstance(save_only, bool): |
968 illegal_value(save_only, 'save_only', 'quickImshow') | 1476 illegal_value(save_only, 'save_only', 'quick_imshow') |
969 return | 1477 return |
970 if not isinstance(clear, bool): | 1478 if not isinstance(clear, bool): |
971 illegal_value(clear, 'clear', 'quickImshow') | 1479 illegal_value(clear, 'clear', 'quick_imshow') |
1480 return | |
1481 if not isinstance(block, bool): | |
1482 illegal_value(block, 'block', 'quick_imshow') | |
972 return | 1483 return |
973 if not title: | 1484 if not title: |
974 title='quick imshow' | 1485 title='quick imshow' |
975 # else: | 1486 # else: |
976 # title = re.sub(r"\s+", '_', title) | 1487 # title = re.sub(r"\s+", '_', title) |
983 else: | 1494 else: |
984 if path is None: | 1495 if path is None: |
985 path = name | 1496 path = name |
986 else: | 1497 else: |
987 path = f'{path}/{name}' | 1498 path = f'{path}/{name}' |
1499 if 'cmap' in kwargs and a.ndim == 3 and (a.shape[2] == 3 or a.shape[2] == 4): | |
1500 use_cmap = True | |
1501 if a.shape[2] == 4 and a[:,:,-1].min() != a[:,:,-1].max(): | |
1502 use_cmap = False | |
1503 if any(True if a[i,j,0] != a[i,j,1] and a[i,j,0] != a[i,j,2] else False | |
1504 for i in range(a.shape[0]) for j in range(a.shape[1])): | |
1505 use_cmap = False | |
1506 if use_cmap: | |
1507 a = a[:,:,0] | |
1508 else: | |
1509 logger.warning('Image incompatible with cmap option, ignore cmap') | |
1510 kwargs.pop('cmap') | |
988 if extent is None: | 1511 if extent is None: |
989 extent = (0, a.shape[1], a.shape[0], 0) | 1512 extent = (0, a.shape[1], a.shape[0], 0) |
990 if clear: | 1513 if clear: |
991 plt.close(fig=title) | 1514 try: |
1515 plt.close(fig=title) | |
1516 except: | |
1517 pass | |
992 if not save_only: | 1518 if not save_only: |
993 plt.ion() | 1519 if block: |
1520 plt.ioff() | |
1521 else: | |
1522 plt.ion() | |
994 plt.figure(title) | 1523 plt.figure(title) |
995 plt.imshow(a, extent=extent, **kwargs) | 1524 plt.imshow(a, extent=extent, **kwargs) |
996 if show_grid: | 1525 if show_grid: |
997 ax = plt.gca() | 1526 ax = plt.gca() |
998 ax.grid(color=grid_color, linewidth=grid_linewidth) | 1527 ax.grid(color=grid_color, linewidth=grid_linewidth) |
1002 plt.savefig(path) | 1531 plt.savefig(path) |
1003 plt.close(fig=title) | 1532 plt.close(fig=title) |
1004 else: | 1533 else: |
1005 if save_fig: | 1534 if save_fig: |
1006 plt.savefig(path) | 1535 plt.savefig(path) |
1007 | 1536 if block: |
1008 def quickPlot(*args, xerr=None, yerr=None, vlines=None, title=None, xlim=None, ylim=None, | 1537 plt.show(block=block) |
1009 xlabel=None, ylabel=None, legend=None, path=None, name=None, show_grid=False, | 1538 |
1539 def quick_plot(*args, xerr=None, yerr=None, vlines=None, title=None, xlim=None, ylim=None, | |
1540 xlabel=None, ylabel=None, legend=None, path=None, name=None, show_grid=False, | |
1010 save_fig=False, save_only=False, clear=True, block=False, **kwargs): | 1541 save_fig=False, save_only=False, clear=True, block=False, **kwargs): |
1011 if title is not None and not isinstance(title, str): | 1542 if title is not None and not isinstance(title, str): |
1012 illegal_value(title, 'title', 'quickPlot') | 1543 illegal_value(title, 'title', 'quick_plot') |
1013 title = None | 1544 title = None |
1014 if xlim is not None and not isinstance(xlim, (tuple, list)) and len(xlim) != 2: | 1545 if xlim is not None and not isinstance(xlim, (tuple, list)) and len(xlim) != 2: |
1015 illegal_value(xlim, 'xlim', 'quickPlot') | 1546 illegal_value(xlim, 'xlim', 'quick_plot') |
1016 xlim = None | 1547 xlim = None |
1017 if ylim is not None and not isinstance(ylim, (tuple, list)) and len(ylim) != 2: | 1548 if ylim is not None and not isinstance(ylim, (tuple, list)) and len(ylim) != 2: |
1018 illegal_value(ylim, 'ylim', 'quickPlot') | 1549 illegal_value(ylim, 'ylim', 'quick_plot') |
1019 ylim = None | 1550 ylim = None |
1020 if xlabel is not None and not isinstance(xlabel, str): | 1551 if xlabel is not None and not isinstance(xlabel, str): |
1021 illegal_value(xlabel, 'xlabel', 'quickPlot') | 1552 illegal_value(xlabel, 'xlabel', 'quick_plot') |
1022 xlabel = None | 1553 xlabel = None |
1023 if ylabel is not None and not isinstance(ylabel, str): | 1554 if ylabel is not None and not isinstance(ylabel, str): |
1024 illegal_value(ylabel, 'ylabel', 'quickPlot') | 1555 illegal_value(ylabel, 'ylabel', 'quick_plot') |
1025 ylabel = None | 1556 ylabel = None |
1026 if legend is not None and not isinstance(legend, (tuple, list)): | 1557 if legend is not None and not isinstance(legend, (tuple, list)): |
1027 illegal_value(legend, 'legend', 'quickPlot') | 1558 illegal_value(legend, 'legend', 'quick_plot') |
1028 legend = None | 1559 legend = None |
1029 if path is not None and not isinstance(path, str): | 1560 if path is not None and not isinstance(path, str): |
1030 illegal_value(path, 'path', 'quickPlot') | 1561 illegal_value(path, 'path', 'quick_plot') |
1031 return | 1562 return |
1032 if not isinstance(show_grid, bool): | 1563 if not isinstance(show_grid, bool): |
1033 illegal_value(show_grid, 'show_grid', 'quickPlot') | 1564 illegal_value(show_grid, 'show_grid', 'quick_plot') |
1034 return | 1565 return |
1035 if not isinstance(save_fig, bool): | 1566 if not isinstance(save_fig, bool): |
1036 illegal_value(save_fig, 'save_fig', 'quickPlot') | 1567 illegal_value(save_fig, 'save_fig', 'quick_plot') |
1037 return | 1568 return |
1038 if not isinstance(save_only, bool): | 1569 if not isinstance(save_only, bool): |
1039 illegal_value(save_only, 'save_only', 'quickPlot') | 1570 illegal_value(save_only, 'save_only', 'quick_plot') |
1040 return | 1571 return |
1041 if not isinstance(clear, bool): | 1572 if not isinstance(clear, bool): |
1042 illegal_value(clear, 'clear', 'quickPlot') | 1573 illegal_value(clear, 'clear', 'quick_plot') |
1043 return | 1574 return |
1044 if not isinstance(block, bool): | 1575 if not isinstance(block, bool): |
1045 illegal_value(block, 'block', 'quickPlot') | 1576 illegal_value(block, 'block', 'quick_plot') |
1046 return | 1577 return |
1047 if title is None: | 1578 if title is None: |
1048 title = 'quick plot' | 1579 title = 'quick plot' |
1049 # else: | 1580 # else: |
1050 # title = re.sub(r"\s+", '_', title) | 1581 # title = re.sub(r"\s+", '_', title) |
1058 if path is None: | 1589 if path is None: |
1059 path = name | 1590 path = name |
1060 else: | 1591 else: |
1061 path = f'{path}/{name}' | 1592 path = f'{path}/{name}' |
1062 if clear: | 1593 if clear: |
1063 plt.close(fig=title) | 1594 try: |
1595 plt.close(fig=title) | |
1596 except: | |
1597 pass | |
1064 args = unwrap_tuple(args) | 1598 args = unwrap_tuple(args) |
1065 if depth_tuple(args) > 1 and (xerr is not None or yerr is not None): | 1599 if depth_tuple(args) > 1 and (xerr is not None or yerr is not None): |
1066 logging.warning('Error bars ignored form multiple curves') | 1600 logger.warning('Error bars ignored form multiple curves') |
1067 if not save_only: | 1601 if not save_only: |
1068 if block: | 1602 if block: |
1069 plt.ioff() | 1603 plt.ioff() |
1070 else: | 1604 else: |
1071 plt.ion() | 1605 plt.ion() |
1077 if xerr is None and yerr is None: | 1611 if xerr is None and yerr is None: |
1078 plt.plot(*args, **kwargs) | 1612 plt.plot(*args, **kwargs) |
1079 else: | 1613 else: |
1080 plt.errorbar(*args, xerr=xerr, yerr=yerr, **kwargs) | 1614 plt.errorbar(*args, xerr=xerr, yerr=yerr, **kwargs) |
1081 if vlines is not None: | 1615 if vlines is not None: |
1616 if isinstance(vlines, (int, float)): | |
1617 vlines = [vlines] | |
1082 for v in vlines: | 1618 for v in vlines: |
1083 plt.axvline(v, color='r', linestyle='--', **kwargs) | 1619 plt.axvline(v, color='r', linestyle='--', **kwargs) |
1084 # if vlines is not None: | 1620 # if vlines is not None: |
1085 # for s in tuple(([x, x], list(plt.gca().get_ylim())) for x in vlines): | 1621 # for s in tuple(([x, x], list(plt.gca().get_ylim())) for x in vlines): |
1086 # plt.plot(*s, color='red', **kwargs) | 1622 # plt.plot(*s, color='red', **kwargs) |
1104 if save_fig: | 1640 if save_fig: |
1105 plt.savefig(path) | 1641 plt.savefig(path) |
1106 if block: | 1642 if block: |
1107 plt.show(block=block) | 1643 plt.show(block=block) |
1108 | 1644 |
1109 def selectArrayBounds(a, x_low=None, x_upp=None, num_x_min=None, ask_bounds=False, | 1645 def select_array_bounds(a, x_low=None, x_upp=None, num_x_min=None, ask_bounds=False, |
1110 title='select array bounds'): | 1646 title='select array bounds'): |
1111 """Interactively select the lower and upper data bounds for a numpy array. | 1647 """Interactively select the lower and upper data bounds for a numpy array. |
1112 """ | 1648 """ |
1113 if isinstance(a, (tuple, list)): | 1649 if isinstance(a, (tuple, list)): |
1114 a = np.array(a) | 1650 a = np.array(a) |
1115 if not isinstance(a, np.ndarray) or a.ndim != 1: | 1651 if not isinstance(a, np.ndarray) or a.ndim != 1: |
1116 illegal_value(a.ndim, 'array type or dimension', 'selectArrayBounds') | 1652 illegal_value(a.ndim, 'array type or dimension', 'select_array_bounds') |
1117 return None | 1653 return(None) |
1118 len_a = len(a) | 1654 len_a = len(a) |
1119 if num_x_min is None: | 1655 if num_x_min is None: |
1120 num_x_min = 1 | 1656 num_x_min = 1 |
1121 else: | 1657 else: |
1122 if num_x_min < 2 or num_x_min > len_a: | 1658 if num_x_min < 2 or num_x_min > len_a: |
1123 logging.warning('Illegal value for num_x_min in selectArrayBounds, input ignored') | 1659 logger.warning('Invalid value for num_x_min in select_array_bounds, input ignored') |
1124 num_x_min = 1 | 1660 num_x_min = 1 |
1125 | 1661 |
1126 # Ask to use current bounds | 1662 # Ask to use current bounds |
1127 if ask_bounds and (x_low is not None or x_upp is not None): | 1663 if ask_bounds and (x_low is not None or x_upp is not None): |
1128 if x_low is None: | 1664 if x_low is None: |
1129 x_low = 0 | 1665 x_low = 0 |
1130 if not is_int(x_low, 0, len_a-num_x_min): | 1666 if not is_int(x_low, ge=0, le=len_a-num_x_min): |
1131 illegal_value(x_low, 'x_low', 'selectArrayBounds') | 1667 illegal_value(x_low, 'x_low', 'select_array_bounds') |
1132 return None | 1668 return(None) |
1133 if x_upp is None: | 1669 if x_upp is None: |
1134 x_upp = len_a | 1670 x_upp = len_a |
1135 if not is_int(x_upp, x_low+num_x_min, len_a): | 1671 if not is_int(x_upp, ge=x_low+num_x_min, le=len_a): |
1136 illegal_value(x_upp, 'x_upp', 'selectArrayBounds') | 1672 illegal_value(x_upp, 'x_upp', 'select_array_bounds') |
1137 return None | 1673 return(None) |
1138 quickPlot((range(len_a), a), vlines=(x_low,x_upp), title=title) | 1674 quick_plot((range(len_a), a), vlines=(x_low,x_upp), title=title) |
1139 if not input_yesno(f'\nCurrent array bounds: [{x_low}, {x_upp}] '+ | 1675 if not input_yesno(f'\nCurrent array bounds: [{x_low}, {x_upp}] '+ |
1140 'use these values (y/n)?', 'y'): | 1676 'use these values (y/n)?', 'y'): |
1141 x_low = None | 1677 x_low = None |
1142 x_upp = None | 1678 x_upp = None |
1143 else: | 1679 else: |
1144 clearPlot(title) | 1680 clear_plot(title) |
1145 return x_low, x_upp | 1681 return(x_low, x_upp) |
1146 | 1682 |
1147 if x_low is None: | 1683 if x_low is None: |
1148 x_min = 0 | 1684 x_min = 0 |
1149 x_max = len_a | 1685 x_max = len_a |
1150 x_low_max = len_a-num_x_min | 1686 x_low_max = len_a-num_x_min |
1151 while True: | 1687 while True: |
1152 quickPlot(range(x_min, x_max), a[x_min:x_max], title=title) | 1688 quick_plot(range(x_min, x_max), a[x_min:x_max], title=title) |
1153 zoom_flag = input_yesno('Set lower data bound (y) or zoom in (n)?', 'y') | 1689 zoom_flag = input_yesno('Set lower data bound (y) or zoom in (n)?', 'y') |
1154 if zoom_flag: | 1690 if zoom_flag: |
1155 x_low = input_int(' Set lower data bound', 0, x_low_max) | 1691 x_low = input_int(' Set lower data bound', ge=0, le=x_low_max) |
1156 break | 1692 break |
1157 else: | 1693 else: |
1158 x_min = input_int(' Set lower zoom index', 0, x_low_max) | 1694 x_min = input_int(' Set lower zoom index', ge=0, le=x_low_max) |
1159 x_max = input_int(' Set upper zoom index', x_min+1, x_low_max+1) | 1695 x_max = input_int(' Set upper zoom index', ge=x_min+1, le=x_low_max+1) |
1160 else: | 1696 else: |
1161 if not is_int(x_low, 0, len_a-num_x_min): | 1697 if not is_int(x_low, ge=0, le=len_a-num_x_min): |
1162 illegal_value(x_low, 'x_low', 'selectArrayBounds') | 1698 illegal_value(x_low, 'x_low', 'select_array_bounds') |
1163 return None | 1699 return(None) |
1164 if x_upp is None: | 1700 if x_upp is None: |
1165 x_min = x_low+num_x_min | 1701 x_min = x_low+num_x_min |
1166 x_max = len_a | 1702 x_max = len_a |
1167 x_upp_min = x_min | 1703 x_upp_min = x_min |
1168 while True: | 1704 while True: |
1169 quickPlot(range(x_min, x_max), a[x_min:x_max], title=title) | 1705 quick_plot(range(x_min, x_max), a[x_min:x_max], title=title) |
1170 zoom_flag = input_yesno('Set upper data bound (y) or zoom in (n)?', 'y') | 1706 zoom_flag = input_yesno('Set upper data bound (y) or zoom in (n)?', 'y') |
1171 if zoom_flag: | 1707 if zoom_flag: |
1172 x_upp = input_int(' Set upper data bound', x_upp_min, len_a) | 1708 x_upp = input_int(' Set upper data bound', ge=x_upp_min, le=len_a) |
1173 break | 1709 break |
1174 else: | 1710 else: |
1175 x_min = input_int(' Set upper zoom index', x_upp_min, len_a-1) | 1711 x_min = input_int(' Set upper zoom index', ge=x_upp_min, le=len_a-1) |
1176 x_max = input_int(' Set upper zoom index', x_min+1, len_a) | 1712 x_max = input_int(' Set upper zoom index', ge=x_min+1, le=len_a) |
1177 else: | 1713 else: |
1178 if not is_int(x_upp, x_low+num_x_min, len_a): | 1714 if not is_int(x_upp, ge=x_low+num_x_min, le=len_a): |
1179 illegal_value(x_upp, 'x_upp', 'selectArrayBounds') | 1715 illegal_value(x_upp, 'x_upp', 'select_array_bounds') |
1180 return None | 1716 return(None) |
1181 print(f'lower bound = {x_low} (inclusive)\nupper bound = {x_upp} (exclusive)]') | 1717 print(f'lower bound = {x_low} (inclusive)\nupper bound = {x_upp} (exclusive)]') |
1182 quickPlot((range(len_a), a), vlines=(x_low,x_upp), title=title) | 1718 quick_plot((range(len_a), a), vlines=(x_low,x_upp), title=title) |
1183 if not input_yesno('Accept these bounds (y/n)?', 'y'): | 1719 if not input_yesno('Accept these bounds (y/n)?', 'y'): |
1184 x_low, x_upp = selectArrayBounds(a, None, None, num_x_min, title=title) | 1720 x_low, x_upp = select_array_bounds(a, None, None, num_x_min, title=title) |
1185 clearPlot(title) | 1721 clear_plot(title) |
1186 return x_low, x_upp | 1722 return(x_low, x_upp) |
1187 | 1723 |
1188 def selectImageBounds(a, axis, low=None, upp=None, num_min=None, | 1724 def select_image_bounds(a, axis, low=None, upp=None, num_min=None, title='select array bounds', |
1189 title='select array bounds'): | 1725 raise_error=False): |
1190 """Interactively select the lower and upper data bounds for a 2D numpy array. | 1726 """Interactively select the lower and upper data bounds for a 2D numpy array. |
1191 """ | 1727 """ |
1192 if isinstance(a, np.ndarray): | 1728 a = np.asarray(a) |
1193 if a.ndim != 2: | 1729 if a.ndim != 2: |
1194 illegal_value(a.ndim, 'array dimension', 'selectImageBounds') | 1730 illegal_value(a.ndim, 'array dimension', location='select_image_bounds', |
1195 return None | 1731 raise_error=raise_error) |
1196 elif isinstance(a, (tuple, list)): | 1732 return(None) |
1197 if len(a) != 2: | |
1198 illegal_value(len(a), 'array dimension', 'selectImageBounds') | |
1199 return None | |
1200 if len(a[0]) != len(a[1]) or not (isinstance(a[0], (tuple, list, np.ndarray)) and | |
1201 isinstance(a[1], (tuple, list, np.ndarray))): | |
1202 logging.error(f'Illegal array type in selectImageBounds ({type(a[0])} {type(a[1])})') | |
1203 return None | |
1204 a = np.array(a) | |
1205 else: | |
1206 illegal_value(a, 'array type', 'selectImageBounds') | |
1207 return None | |
1208 if axis < 0 or axis >= a.ndim: | 1733 if axis < 0 or axis >= a.ndim: |
1209 illegal_value(axis, 'axis', 'selectImageBounds') | 1734 illegal_value(axis, 'axis', location='select_image_bounds', raise_error=raise_error) |
1210 return None | 1735 return(None) |
1211 low_save = low | 1736 low_save = low |
1212 upp_save = upp | 1737 upp_save = upp |
1213 num_min_save = num_min | 1738 num_min_save = num_min |
1214 if num_min is None: | 1739 if num_min is None: |
1215 num_min = 1 | 1740 num_min = 1 |
1216 else: | 1741 else: |
1217 if num_min < 2 or num_min > a.shape[axis]: | 1742 if num_min < 2 or num_min > a.shape[axis]: |
1218 logging.warning('Illegal input for num_min in selectImageBounds, input ignored') | 1743 logger.warning('Invalid input for num_min in select_image_bounds, input ignored') |
1219 num_min = 1 | 1744 num_min = 1 |
1220 if low is None: | 1745 if low is None: |
1221 min_ = 0 | 1746 min_ = 0 |
1222 max_ = a.shape[axis] | 1747 max_ = a.shape[axis] |
1223 low_max = a.shape[axis]-num_min | 1748 low_max = a.shape[axis]-num_min |
1224 while True: | 1749 while True: |
1225 if axis: | 1750 if axis: |
1226 quickImshow(a[:,min_:max_], title=title, aspect='auto', | 1751 quick_imshow(a[:,min_:max_], title=title, aspect='auto', |
1227 extent=[min_,max_,a.shape[0],0]) | 1752 extent=[min_,max_,a.shape[0],0]) |
1228 else: | 1753 else: |
1229 quickImshow(a[min_:max_,:], title=title, aspect='auto', | 1754 quick_imshow(a[min_:max_,:], title=title, aspect='auto', |
1230 extent=[0,a.shape[1], max_,min_]) | 1755 extent=[0,a.shape[1], max_,min_]) |
1231 zoom_flag = input_yesno('Set lower data bound (y) or zoom in (n)?', 'y') | 1756 zoom_flag = input_yesno('Set lower data bound (y) or zoom in (n)?', 'y') |
1232 if zoom_flag: | 1757 if zoom_flag: |
1233 low = input_int(' Set lower data bound', 0, low_max) | 1758 low = input_int(' Set lower data bound', ge=0, le=low_max) |
1234 break | 1759 break |
1235 else: | 1760 else: |
1236 min_ = input_int(' Set lower zoom index', 0, low_max) | 1761 min_ = input_int(' Set lower zoom index', ge=0, le=low_max) |
1237 max_ = input_int(' Set upper zoom index', min_+1, low_max+1) | 1762 max_ = input_int(' Set upper zoom index', ge=min_+1, le=low_max+1) |
1238 else: | 1763 else: |
1239 if not is_int(low, 0, a.shape[axis]-num_min): | 1764 if not is_int(low, ge=0, le=a.shape[axis]-num_min): |
1240 illegal_value(low, 'low', 'selectImageBounds') | 1765 illegal_value(low, 'low', location='select_image_bounds', raise_error=raise_error) |
1241 return None | 1766 return(None) |
1242 if upp is None: | 1767 if upp is None: |
1243 min_ = low+num_min | 1768 min_ = low+num_min |
1244 max_ = a.shape[axis] | 1769 max_ = a.shape[axis] |
1245 upp_min = min_ | 1770 upp_min = min_ |
1246 while True: | 1771 while True: |
1247 if axis: | 1772 if axis: |
1248 quickImshow(a[:,min_:max_], title=title, aspect='auto', | 1773 quick_imshow(a[:,min_:max_], title=title, aspect='auto', |
1249 extent=[min_,max_,a.shape[0],0]) | 1774 extent=[min_,max_,a.shape[0],0]) |
1250 else: | 1775 else: |
1251 quickImshow(a[min_:max_,:], title=title, aspect='auto', | 1776 quick_imshow(a[min_:max_,:], title=title, aspect='auto', |
1252 extent=[0,a.shape[1], max_,min_]) | 1777 extent=[0,a.shape[1], max_,min_]) |
1253 zoom_flag = input_yesno('Set upper data bound (y) or zoom in (n)?', 'y') | 1778 zoom_flag = input_yesno('Set upper data bound (y) or zoom in (n)?', 'y') |
1254 if zoom_flag: | 1779 if zoom_flag: |
1255 upp = input_int(' Set upper data bound', upp_min, a.shape[axis]) | 1780 upp = input_int(' Set upper data bound', ge=upp_min, le=a.shape[axis]) |
1256 break | 1781 break |
1257 else: | 1782 else: |
1258 min_ = input_int(' Set upper zoom index', upp_min, a.shape[axis]-1) | 1783 min_ = input_int(' Set upper zoom index', ge=upp_min, le=a.shape[axis]-1) |
1259 max_ = input_int(' Set upper zoom index', min_+1, a.shape[axis]) | 1784 max_ = input_int(' Set upper zoom index', ge=min_+1, le=a.shape[axis]) |
1260 else: | 1785 else: |
1261 if not is_int(upp, low+num_min, a.shape[axis]): | 1786 if not is_int(upp, ge=low+num_min, le=a.shape[axis]): |
1262 illegal_value(upp, 'upp', 'selectImageBounds') | 1787 illegal_value(upp, 'upp', location='select_image_bounds', raise_error=raise_error) |
1263 return None | 1788 return(None) |
1264 bounds = (low, upp) | 1789 bounds = (low, upp) |
1265 a_tmp = np.copy(a) | 1790 a_tmp = np.copy(a) |
1266 a_tmp_max = a.max() | 1791 a_tmp_max = a.max() |
1267 if axis: | 1792 if axis: |
1268 a_tmp[:,bounds[0]] = a_tmp_max | 1793 a_tmp[:,bounds[0]] = a_tmp_max |
1269 a_tmp[:,bounds[1]-1] = a_tmp_max | 1794 a_tmp[:,bounds[1]-1] = a_tmp_max |
1270 else: | 1795 else: |
1271 a_tmp[bounds[0],:] = a_tmp_max | 1796 a_tmp[bounds[0],:] = a_tmp_max |
1272 a_tmp[bounds[1]-1,:] = a_tmp_max | 1797 a_tmp[bounds[1]-1,:] = a_tmp_max |
1273 print(f'lower bound = {low} (inclusive)\nupper bound = {upp} (exclusive)') | 1798 print(f'lower bound = {low} (inclusive)\nupper bound = {upp} (exclusive)') |
1274 quickImshow(a_tmp, title=title) | 1799 quick_imshow(a_tmp, title=title, aspect='auto') |
1275 del a_tmp | 1800 del a_tmp |
1276 if not input_yesno('Accept these bounds (y/n)?', 'y'): | 1801 if not input_yesno('Accept these bounds (y/n)?', 'y'): |
1277 bounds = selectImageBounds(a, axis, low=low_save, upp=upp_save, num_min=num_min_save, | 1802 bounds = select_image_bounds(a, axis, low=low_save, upp=upp_save, num_min=num_min_save, |
1278 title=title) | 1803 title=title) |
1279 return bounds | 1804 return(bounds) |
1805 | |
1806 def select_one_image_bound(a, axis, bound=None, bound_name=None, title='select array bounds', | |
1807 default='y', raise_error=False): | |
1808 """Interactively select a data boundary for a 2D numpy array. | |
1809 """ | |
1810 a = np.asarray(a) | |
1811 if a.ndim != 2: | |
1812 illegal_value(a.ndim, 'array dimension', location='select_one_image_bound', | |
1813 raise_error=raise_error) | |
1814 return(None) | |
1815 if axis < 0 or axis >= a.ndim: | |
1816 illegal_value(axis, 'axis', location='select_one_image_bound', raise_error=raise_error) | |
1817 return(None) | |
1818 if bound_name is None: | |
1819 bound_name = 'data bound' | |
1820 if bound is None: | |
1821 min_ = 0 | |
1822 max_ = a.shape[axis] | |
1823 bound_max = a.shape[axis]-1 | |
1824 while True: | |
1825 if axis: | |
1826 quick_imshow(a[:,min_:max_], title=title, aspect='auto', | |
1827 extent=[min_,max_,a.shape[0],0]) | |
1828 else: | |
1829 quick_imshow(a[min_:max_,:], title=title, aspect='auto', | |
1830 extent=[0,a.shape[1], max_,min_]) | |
1831 zoom_flag = input_yesno(f'Set {bound_name} (y) or zoom in (n)?', 'y') | |
1832 if zoom_flag: | |
1833 bound = input_int(f' Set {bound_name}', ge=0, le=bound_max) | |
1834 clear_imshow(title) | |
1835 break | |
1836 else: | |
1837 min_ = input_int(' Set lower zoom index', ge=0, le=bound_max) | |
1838 max_ = input_int(' Set upper zoom index', ge=min_+1, le=bound_max+1) | |
1839 | |
1840 elif not is_int(bound, ge=0, le=a.shape[axis]-1): | |
1841 illegal_value(bound, 'bound', location='select_one_image_bound', raise_error=raise_error) | |
1842 return(None) | |
1843 else: | |
1844 print(f'Current {bound_name} = {bound}') | |
1845 a_tmp = np.copy(a) | |
1846 a_tmp_max = a.max() | |
1847 if axis: | |
1848 a_tmp[:,bound] = a_tmp_max | |
1849 else: | |
1850 a_tmp[bound,:] = a_tmp_max | |
1851 quick_imshow(a_tmp, title=title, aspect='auto') | |
1852 del a_tmp | |
1853 if not input_yesno(f'Accept this {bound_name} (y/n)?', default): | |
1854 bound = select_one_image_bound(a, axis, bound_name=bound_name, title=title) | |
1855 clear_imshow(title) | |
1856 return(bound) | |
1280 | 1857 |
1281 | 1858 |
1282 class Config: | 1859 class Config: |
1283 """Base class for processing a config file or dictionary. | 1860 """Base class for processing a config file or dictionary. |
1284 """ | 1861 """ |
1287 self.load_flag = False | 1864 self.load_flag = False |
1288 self.suffix = None | 1865 self.suffix = None |
1289 | 1866 |
1290 # Load config file | 1867 # Load config file |
1291 if config_file is not None and config_dict is not None: | 1868 if config_file is not None and config_dict is not None: |
1292 logging.warning('Ignoring config_dict (both config_file and config_dict are specified)') | 1869 logger.warning('Ignoring config_dict (both config_file and config_dict are specified)') |
1293 if config_file is not None: | 1870 if config_file is not None: |
1294 self.loadFile(config_file) | 1871 self.load_file(config_file) |
1295 elif config_dict is not None: | 1872 elif config_dict is not None: |
1296 self.loadDict(config_dict) | 1873 self.load_dict(config_dict) |
1297 | 1874 |
1298 def loadFile(self, config_file): | 1875 def load_file(self, config_file): |
1299 """Load a config file. | 1876 """Load a config file. |
1300 """ | 1877 """ |
1301 if self.load_flag: | 1878 if self.load_flag: |
1302 logging.warning('Overwriting any previously loaded config file') | 1879 logger.warning('Overwriting any previously loaded config file') |
1303 self.config = {} | 1880 self.config = {} |
1304 | 1881 |
1305 # Ensure config file exists | 1882 # Ensure config file exists |
1306 if not os.path.isfile(config_file): | 1883 if not os.path.isfile(config_file): |
1307 logging.error(f'Unable to load {config_file}') | 1884 logger.error(f'Unable to load {config_file}') |
1308 return | 1885 return |
1309 | 1886 |
1310 # Load config file (for now for Galaxy, allow .dat extension) | 1887 # Load config file (for now for Galaxy, allow .dat extension) |
1311 self.suffix = os.path.splitext(config_file)[1] | 1888 self.suffix = os.path.splitext(config_file)[1] |
1312 if self.suffix == '.yml' or self.suffix == '.yaml' or self.suffix == '.dat': | 1889 if self.suffix == '.yml' or self.suffix == '.yaml' or self.suffix == '.dat': |
1313 with open(config_file, 'r') as f: | 1890 with open(config_file, 'r') as f: |
1314 self.config = yaml.safe_load(f) | 1891 self.config = safe_load(f) |
1315 elif self.suffix == '.txt': | 1892 elif self.suffix == '.txt': |
1316 with open(config_file, 'r') as f: | 1893 with open(config_file, 'r') as f: |
1317 lines = f.read().splitlines() | 1894 lines = f.read().splitlines() |
1318 self.config = {item[0].strip():literal_eval(item[1].strip()) for item in | 1895 self.config = {item[0].strip():literal_eval(item[1].strip()) for item in |
1319 [line.split('#')[0].split('=') for line in lines if '=' in line.split('#')[0]]} | 1896 [line.split('#')[0].split('=') for line in lines if '=' in line.split('#')[0]]} |
1320 else: | 1897 else: |
1321 illegal_value(self.suffix, 'config file extension', 'Config.loadFile') | 1898 illegal_value(self.suffix, 'config file extension', 'Config.load_file') |
1322 | 1899 |
1323 # Make sure config file was correctly loaded | 1900 # Make sure config file was correctly loaded |
1324 if isinstance(self.config, dict): | 1901 if isinstance(self.config, dict): |
1325 self.load_flag = True | 1902 self.load_flag = True |
1326 else: | 1903 else: |
1327 logging.error(f'Unable to load dictionary from config file: {config_file}') | 1904 logger.error(f'Unable to load dictionary from config file: {config_file}') |
1328 self.config = {} | 1905 self.config = {} |
1329 | 1906 |
1330 def loadDict(self, config_dict): | 1907 def load_dict(self, config_dict): |
1331 """Takes a dictionary and places it into self.config. | 1908 """Takes a dictionary and places it into self.config. |
1332 """ | 1909 """ |
1333 if self.load_flag: | 1910 if self.load_flag: |
1334 logging.warning('Overwriting the previously loaded config file') | 1911 logger.warning('Overwriting the previously loaded config file') |
1335 | 1912 |
1336 if isinstance(config_dict, dict): | 1913 if isinstance(config_dict, dict): |
1337 self.config = config_dict | 1914 self.config = config_dict |
1338 self.load_flag = True | 1915 self.load_flag = True |
1339 else: | 1916 else: |
1340 illegal_value(config_dict, 'dictionary config object', 'Config.loadDict') | 1917 illegal_value(config_dict, 'dictionary config object', 'Config.load_dict') |
1341 self.config = {} | 1918 self.config = {} |
1342 | 1919 |
1343 def saveFile(self, config_file): | 1920 def save_file(self, config_file): |
1344 """Save the config file (as a yaml file only right now). | 1921 """Save the config file (as a yaml file only right now). |
1345 """ | 1922 """ |
1346 suffix = os.path.splitext(config_file)[1] | 1923 suffix = os.path.splitext(config_file)[1] |
1347 if suffix != '.yml' and suffix != '.yaml': | 1924 if suffix != '.yml' and suffix != '.yaml': |
1348 illegal_value(suffix, 'config file extension', 'Config.saveFile') | 1925 illegal_value(suffix, 'config file extension', 'Config.save_file') |
1349 | 1926 |
1350 # Check if config file exists | 1927 # Check if config file exists |
1351 if os.path.isfile(config_file): | 1928 if os.path.isfile(config_file): |
1352 logging.info(f'Updating {config_file}') | 1929 logger.info(f'Updating {config_file}') |
1353 else: | 1930 else: |
1354 logging.info(f'Saving {config_file}') | 1931 logger.info(f'Saving {config_file}') |
1355 | 1932 |
1356 # Save config file | 1933 # Save config file |
1357 with open(config_file, 'w') as f: | 1934 with open(config_file, 'w') as f: |
1358 yaml.safe_dump(self.config, f) | 1935 safe_dump(self.config, f) |
1359 | 1936 |
1360 def validate(self, pars_required, pars_missing=None): | 1937 def validate(self, pars_required, pars_missing=None): |
1361 """Returns False if any required keys are missing. | 1938 """Returns False if any required keys are missing. |
1362 """ | 1939 """ |
1363 if not self.load_flag: | 1940 if not self.load_flag: |
1364 logging.error('Load a config file prior to calling Config.validate') | 1941 logger.error('Load a config file prior to calling Config.validate') |
1365 | 1942 |
1366 def validate_nested_pars(config, par): | 1943 def validate_nested_pars(config, par): |
1367 par_levels = par.split(':') | 1944 par_levels = par.split(':') |
1368 first_level_par = par_levels[0] | 1945 first_level_par = par_levels[0] |
1369 try: | 1946 try: |
1372 pass | 1949 pass |
1373 try: | 1950 try: |
1374 next_level_config = config[first_level_par] | 1951 next_level_config = config[first_level_par] |
1375 if len(par_levels) > 1: | 1952 if len(par_levels) > 1: |
1376 next_level_par = ':'.join(par_levels[1:]) | 1953 next_level_par = ':'.join(par_levels[1:]) |
1377 return validate_nested_pars(next_level_config, next_level_par) | 1954 return(validate_nested_pars(next_level_config, next_level_par)) |
1378 else: | 1955 else: |
1379 return True | 1956 return(True) |
1380 except: | 1957 except: |
1381 return False | 1958 return(False) |
1382 | 1959 |
1383 pars_missing = [p for p in pars_required if not validate_nested_pars(self.config, p)] | 1960 pars_missing = [p for p in pars_required if not validate_nested_pars(self.config, p)] |
1384 if len(pars_missing) > 0: | 1961 if len(pars_missing) > 0: |
1385 logging.error(f'Missing item(s) in configuration: {", ".join(pars_missing)}') | 1962 logger.error(f'Missing item(s) in configuration: {", ".join(pars_missing)}') |
1386 return False | 1963 return(False) |
1387 else: | 1964 else: |
1388 return True | 1965 return(True) |