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)