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