Mercurial > repos > rv43 > tomo
diff general.py @ 65:f31ef7bfb430 draft
"planemo upload for repository https://github.com/rolfverberg/galaxytools commit d55db09b45d0b542f966cef17892858bb55d94f7"
author | rv43 |
---|---|
date | Thu, 18 Aug 2022 14:57:39 +0000 |
parents | 98a83f03d91b |
children | ba5866d0251d |
line wrap: on
line diff
--- a/general.py Thu Aug 18 14:49:16 2022 +0000 +++ b/general.py Thu Aug 18 14:57:39 2022 +0000 @@ -23,10 +23,6 @@ from matplotlib.widgets import Button except: pass -try: - import pyinputplus as pyip -except: - pass from ast import literal_eval from copy import deepcopy @@ -292,7 +288,7 @@ else: return int(mo.group()) -def input_int(s=None, v_min=None, v_max=None, default=None): +def input_int(s=None, v_min=None, v_max=None, default=None, inset=None): if default is not None: if not isinstance(default, int): illegal_value(default, 'default', 'input_int') @@ -302,14 +298,14 @@ default_string = '' if v_min is not None: if not isinstance(v_min, int): - illegal_value(vmin, 'vmin', 'input_int') + illegal_value(v_min, 'v_min', 'input_int') return None if default is not None and default < v_min: logging.error('Illegal v_min, default combination ({v_min}, {default})') return None if v_max is not None: if not isinstance(v_max, int): - illegal_value(vmax, 'vmax', 'input_int') + illegal_value(v_max, 'v_max', 'input_int') return None if v_min is not None and v_min > v_max: logging.error(f'Illegal v_min, v_max combination ({v_min}, {v_max})') @@ -317,8 +313,13 @@ if default is not None and default > v_max: logging.error('Illegal default, v_max combination ({default}, {v_max})') return None + if inset is not None: + if (not isinstance(inset, (tuple, list)) or False in [True if isinstance(i, int) else + False for i in inset]): + illegal_value(inset, 'inset', 'input_int') + return None if v_min is not None and v_max is not None: - v_range = f' (in range [{v_min}, {v_max}])' + v_range = f' ({v_min}, {v_max})' elif v_min is not None: v_range = f' (>= {v_min})' elif v_max is not None: @@ -333,8 +334,11 @@ i = input() if isinstance(i, str) and not len(i): v = default + print(f'{v}') else: v = literal_eval(i) + if inset and v not in inset: + raise ValueError(f'{v} not part of the set {inset}') except (ValueError, TypeError, SyntaxError, MemoryError, RecursionError): v = None except: @@ -371,7 +375,7 @@ logging.error('Illegal default, v_max combination ({default}, {v_max})') return None if v_min is not None and v_max is not None: - v_range = f' (in range [{v_min}, {v_max}])' + v_range = f' ({v_min}, {v_max})' elif v_min is not None: v_range = f' (>= {v_min})' elif v_max is not None: @@ -386,6 +390,7 @@ i = input() if isinstance(i, str) and not len(i): v = default + print(f'{v}') else: v = literal_eval(i) except (ValueError, TypeError, SyntaxError, MemoryError, RecursionError): @@ -410,7 +415,7 @@ logging.error(f'Illegal v_min, v_max combination ({v_min}, {v_max})') return None if v_min is not None and v_max is not None: - v_range = f' (each value in range [{v_min}, {v_max}])' + v_range = f' (each value in ({v_min}, {v_max}))' elif v_min is not None: v_range = f' (each value >= {v_min})' elif v_max is not None: @@ -457,15 +462,55 @@ i = input() if isinstance(i, str) and not len(i): i = default - if i.lower() in 'yes': + print(f'{i}') + if i is not None and i.lower() in 'yes': v = True - elif i.lower() in 'no': + elif i is not None and i.lower() in 'no': v = False else: print('Illegal input, enter yes or no') v = input_yesno(s, default) return v +def input_menu(items, default=None, header=None): + if not isinstance(items, (tuple, list)) or False in [True if isinstance(i, str) else False + for i in items]: + illegal_value(items, 'items', 'input_menu') + return None + if default is not None: + if not (isinstance(default, str) and default in items): + logging.error(f'Illegal value for default ({default}), must be in {items}') + return None + default_string = f' [{items.index(default)+1}]' + else: + default_string = '' + if header is None: + print(f'Choose one of the following items (1, {len(items)}){default_string}:') + else: + print(f'{header} (1, {len(items)}){default_string}:') + for i, choice in enumerate(items): + print(f' {i+1}: {choice}') + try: + choice = input() + if isinstance(choice, str) and not len(choice): + choice = items.index(default) + print(f'{choice+1}') + else: + choice = literal_eval(choice) + if isinstance(choice, int) and 1 <= choice <= len(items): + choice -= 1 + else: + raise ValueError + except (ValueError, TypeError, SyntaxError, MemoryError, RecursionError): + choice = None + except: + print('Unexpected error') + raise + if choice is None: + print(f'Illegal choice, enter a number between 1 and {len(items)}') + choice = input_menu(items, default) + return choice + def create_mask(x, bounds=None, reverse_mask=False, current_mask=None): # bounds is a pair of number in the same units a x if not isinstance(x, (tuple, list, np.ndarray)) or not len(x): @@ -492,10 +537,11 @@ return mask def draw_mask_1d(ydata, xdata=None, current_index_ranges=None, current_mask=None, - select_mask=True): + select_mask=True, num_index_ranges_max=None, title=None, legend=None, test_mode=False): def draw_selections(ax): ax.clear() - ax.set_title(axes_title) + ax.set_title(title) + ax.legend([legend]) ax.plot(xdata, ydata, 'k') for (low, upp) in current_include: xlow = 0.5*(xdata[max(0, low-1)]+xdata[low]) @@ -563,7 +609,7 @@ ydata = np.asarray(ydata) if ydata.ndim > 1: logging.warning(f'Illegal ydata dimension ({ydata.ndim})') - return None + return None, None num_data = ydata.size if xdata is None: xdata = np.arange(num_data) @@ -571,24 +617,34 @@ xdata = np.asarray(xdata, dtype=np.float64) if xdata.ndim > 1 or xdata.size != num_data: logging.warning(f'Illegal xdata shape ({xdata.shape})') - return None + return None, None if not np.all(xdata[:-1] < xdata[1:]): logging.warning('Illegal xdata: must be monotonically increasing') - return None + return None, None if current_index_ranges is not None: if not isinstance(current_index_ranges, (tuple, list)): logging.warning('Illegal current_index_ranges parameter ({current_index_ranges}, '+ f'{type(current_index_ranges)})') - return None + return None, None if not isinstance(select_mask, bool): logging.warning('Illegal select_mask parameter ({select_mask}, {type(select_mask)})') - return None + return None, None + if num_index_ranges_max is not None: + logging.warning('num_index_ranges_max input not yet implemented in draw_mask_1d') + if title is None: + title = 'select ranges of data' + elif not isinstance(title, str): + illegal(title, 'title') + title = '' + if legend is None and not isinstance(title, str): + illegal(legend, 'legend') + legend = None if select_mask: - axes_title = 'Click and drag to select ranges of data you wish to include.' + title = f'Click and drag to {title} you wish to include' selection_color = 'green' else: - axes_title = 'Click and drag to select ranges of data you wish to exclude.' + title = f'Click and drag to {title} you wish to exclude' selection_color = 'red' # Set initial selected mask and the selected/unselected index ranges as needed @@ -635,29 +691,32 @@ if current_include[-1][1] < num_data-1: current_exclude.append((current_include[-1][1]+1, num_data-1)) - # Set up matplotlib figure - fig, ax = plt.subplots() - plt.subplots_adjust(bottom=0.2) - draw_selections(ax) + if not test_mode: - # Set up event handling for click-and-drag range selection - cid_click = fig.canvas.mpl_connect('button_press_event', onclick) - cid_release = fig.canvas.mpl_connect('button_release_event', onrelease) + # Set up matplotlib figure + plt.close('all') + fig, ax = plt.subplots() + plt.subplots_adjust(bottom=0.2) + draw_selections(ax) + + # Set up event handling for click-and-drag range selection + cid_click = fig.canvas.mpl_connect('button_press_event', onclick) + cid_release = fig.canvas.mpl_connect('button_release_event', onrelease) - # Set up confirm / clear range selection buttons - confirm_b = Button(plt.axes([0.75, 0.05, 0.15, 0.075]), 'Confirm') - clear_b = Button(plt.axes([0.59, 0.05, 0.15, 0.075]), 'Clear') - cid_confirm = confirm_b.on_clicked(confirm_selection) - cid_clear = clear_b.on_clicked(clear_last_selection) + # Set up confirm / clear range selection buttons + confirm_b = Button(plt.axes([0.75, 0.05, 0.15, 0.075]), 'Confirm') + clear_b = Button(plt.axes([0.59, 0.05, 0.15, 0.075]), 'Clear') + cid_confirm = confirm_b.on_clicked(confirm_selection) + cid_clear = clear_b.on_clicked(clear_last_selection) - # Show figure - plt.show() + # Show figure + plt.show(block=True) - # Disconnect callbacks when figure is closed - fig.canvas.mpl_disconnect(cid_click) - fig.canvas.mpl_disconnect(cid_release) - confirm_b.disconnect(cid_confirm) - clear_b.disconnect(cid_clear) + # Disconnect callbacks when figure is closed + fig.canvas.mpl_disconnect(cid_click) + fig.canvas.mpl_disconnect(cid_release) + confirm_b.disconnect(cid_confirm) + clear_b.disconnect(cid_clear) # Swap selection depending on select_mask if not select_mask: @@ -726,21 +785,19 @@ else: name = ' ' # Check existing values - use_input = 'no' + use_input = False if (is_int(first_index, 0) and is_int(offset, 0) and is_int(num_imgs, 1)): if offset < 0: - use_input = pyip.inputYesNo('\nCurrent'+name+f'first index = {first_index}, '+ - 'use this value ([y]/n)? ', blank=True) + use_input = input_yesno(f'\nCurrent{name}first index = {first_index}, '+ + 'use this value (y/n)?', 'y') else: - use_input = pyip.inputYesNo('\nCurrent'+name+'first index/offset = '+ - f'{first_index}/{offset}, use these values ([y]/n)? ', - blank=True) + use_input = input_yesno(f'\nCurrent{name}first index/offset = '+ + f'{first_index}/{offset}, use these values (y/n)?', 'y') if num_required is None: - if use_input != 'no': - use_input = pyip.inputYesNo('Current number of'+name+'images = '+ - f'{num_imgs}, use this value ([y]/n)? ', - blank=True) - if use_input != 'no': + if use_input: + use_input = input_yesno(f'Current number of{name}images = '+ + f'{num_imgs}, use this value (y/n)? ', 'y') + if use_input: return first_index, offset, num_imgs # Check range against requirements @@ -766,32 +823,27 @@ use_all = f'Use all ([{first_index}, {last_index}])' pick_offset = 'Pick a first index offset and a number of images' pick_bounds = 'Pick the first and last index' - menuchoice = pyip.inputMenu([use_all, pick_offset, pick_bounds], numbered=True) - if menuchoice == use_all: + choice = input_menu([use_all, pick_offset, pick_bounds], default=pick_offset) + if not choice: offset = 0 - elif menuchoice == pick_offset: - offset = pyip.inputInt('Enter the first index offset'+ - f' [0, {last_index-first_index}]: ', min=0, max=last_index-first_index) + elif choice == 1: + offset = input_int('Enter the first index offset', 0, last_index-first_index) first_index += offset if first_index == last_index: num_imgs = 1 else: - num_imgs = pyip.inputInt(f'Enter the number of images [1, {num_imgs-offset}]: ', - min=1, max=num_imgs-offset) + num_imgs = input_int('Enter the number of images', 1, num_imgs-offset) else: - offset = pyip.inputInt(f'Enter the first index [{first_index}, {last_index}]: ', - min=first_index, max=last_index)-first_index + offset = input_int('Enter the first index', first_index, last_index) first_index += offset - num_imgs = pyip.inputInt(f'Enter the last index [{first_index}, {last_index}]: ', - min=first_index, max=last_index)-first_index+1 + num_imgs = input_int('Enter the last index', first_index, last_index)-first_index+1 else: use_all = f'Use ([{first_index}, {first_index+num_required-1}])' pick_offset = 'Pick the first index offset' - menuchoice = pyip.inputMenu([use_all, pick_offset], numbered=True) + choice = input_menu([use_all, pick_offset], pick_offset) offset = 0 - if menuchoice == pick_offset: - offset = pyip.inputInt('Enter the first index offset'+ - f'[0, {num_imgs-num_required}]: ', min=0, max=num_imgs-num_required) + if choice == 1: + offset = input_int('Enter the first index offset', 0, num_imgs-num_required) first_index += offset num_imgs = num_required @@ -873,7 +925,7 @@ img_x_bounds[0]:img_x_bounds[1],img_y_bounds[0]:img_y_bounds[1]] logging.info(f'... done in {time()-t0:.2f} seconds!') else: - illegal_value(filetype, 'filetype', 'findImageRange') + illegal_value(filetype, 'filetype', 'loadImageStack') return img_stack def combine_tiffs_in_h5(files, num_imgs, h5_filename): @@ -1084,8 +1136,8 @@ illegal_value(x_upp, 'x_upp', 'selectArrayBounds') return None quickPlot((range(len_a), a), vlines=(x_low,x_upp), title=title) - if pyip.inputYesNo(f'\nCurrent array bounds: [{x_low}, {x_upp}], '+ - 'use these values ([y]/n)? ', blank=True) == 'no': + if not input_yesno(f'\nCurrent array bounds: [{x_low}, {x_upp}] '+ + 'use these values (y/n)?', 'y'): x_low = None x_upp = None else: @@ -1098,17 +1150,13 @@ x_low_max = len_a-num_x_min while True: quickPlot(range(x_min, x_max), a[x_min:x_max], title=title) - zoom_flag = pyip.inputInt('Set lower data bound ([0]) or zoom in (1)?: ', - min=0, max=1, blank=True) - if zoom_flag == 1: - x_min = pyip.inputInt(f' Set lower zoom index [0, {x_low_max}]: ', - min=0, max=x_low_max) - x_max = pyip.inputInt(f' Set upper zoom index [{x_min+1}, {x_low_max+1}]: ', - min=x_min+1, max=x_low_max+1) + zoom_flag = input_yesno('Set lower data bound (y) or zoom in (n)?', 'y') + if zoom_flag: + x_low = input_int(' Set lower data bound', 0, x_low_max) + break else: - x_low = pyip.inputInt(f' Set lower data bound [0, {x_low_max}]: ', - min=0, max=x_low_max) - break + x_min = input_int(' Set lower zoom index', 0, x_low_max) + x_max = input_int(' Set upper zoom index', x_min+1, x_low_max+1) else: if not is_int(x_low, 0, len_a-num_x_min): illegal_value(x_low, 'x_low', 'selectArrayBounds') @@ -1119,24 +1167,20 @@ x_upp_min = x_min while True: quickPlot(range(x_min, x_max), a[x_min:x_max], title=title) - zoom_flag = pyip.inputInt('Set upper data bound ([0]) or zoom in (1)?: ', - min=0, max=1, blank=True) - if zoom_flag == 1: - x_min = pyip.inputInt(f' Set upper zoom index [{x_upp_min}, {len_a-1}]: ', - min=x_upp_min, max=len_a-1) - x_max = pyip.inputInt(f' Set upper zoom index [{x_min+1}, {len_a}]: ', - min=x_min+1, max=len_a) + zoom_flag = input_yesno('Set upper data bound (y) or zoom in (n)?', 'y') + if zoom_flag: + x_upp = input_int(' Set upper data bound', x_upp_min, len_a) + break else: - x_upp = pyip.inputInt(f' Set upper data bound [{x_upp_min}, {len_a}]: ', - min=x_upp_min, max=len_a) - break + x_min = input_int(' Set upper zoom index', x_upp_min, len_a-1) + x_max = input_int(' Set upper zoom index', x_min+1, len_a) else: if not is_int(x_upp, x_low+num_x_min, len_a): illegal_value(x_upp, 'x_upp', 'selectArrayBounds') return None print(f'lower bound = {x_low} (inclusive)\nupper bound = {x_upp} (exclusive)]') quickPlot((range(len_a), a), vlines=(x_low,x_upp), title=title) - if pyip.inputYesNo('Accept these bounds ([y]/n)?: ', blank=True) == 'no': + if not input_yesno('Accept these bounds (y/n)?', 'y'): x_low, x_upp = selectArrayBounds(a, None, None, num_x_min, title=title) clearPlot(title) return x_low, x_upp @@ -1184,17 +1228,13 @@ else: quickImshow(a[min_:max_,:], title=title, aspect='auto', extent=[0,a.shape[1], max_,min_]) - zoom_flag = pyip.inputInt('Set lower data bound (0) or zoom in (1)?: ', - min=0, max=1) + zoom_flag = input_yesno('Set lower data bound (y) or zoom in (n)?', 'y') if zoom_flag: - min_ = pyip.inputInt(f' Set lower zoom index [0, {low_max}]: ', - min=0, max=low_max) - max_ = pyip.inputInt(f' Set upper zoom index [{min_+1}, {low_max+1}]: ', - min=min_+1, max=low_max+1) + low = input_int(' Set lower data bound', 0, low_max) + break else: - low = pyip.inputInt(f' Set lower data bound [0, {low_max}]: ', - min=0, max=low_max) - break + min_ = input_int(' Set lower zoom index', 0, low_max) + max_ = input_int(' Set upper zoom index', min_+1, low_max+1) else: if not is_int(low, 0, a.shape[axis]-num_min): illegal_value(low, 'low', 'selectImageBounds') @@ -1210,17 +1250,13 @@ else: quickImshow(a[min_:max_,:], title=title, aspect='auto', extent=[0,a.shape[1], max_,min_]) - zoom_flag = pyip.inputInt('Set upper data bound (0) or zoom in (1)?: ', - min=0, max=1) + zoom_flag = input_yesno('Set upper data bound (y) or zoom in (n)?', 'y') if zoom_flag: - min_ = pyip.inputInt(f' Set upper zoom index [{upp_min}, {a.shape[axis]-1}]: ', - min=upp_min, max=a.shape[axis]-1) - max_ = pyip.inputInt(f' Set upper zoom index [{min_+1}, {a.shape[axis]}]: ', - min=min_+1, max=a.shape[axis]) + upp = input_int(' Set upper data bound', upp_min, a.shape[axis]) + break else: - upp = pyip.inputInt(f' Set upper data bound [{upp_min}, {a.shape[axis]}]: ', - min=upp_min, max=a.shape[axis]) - break + min_ = input_int(' Set upper zoom index', upp_min, a.shape[axis]-1) + max_ = input_int(' Set upper zoom index', min_+1, a.shape[axis]) else: if not is_int(upp, low+num_min, a.shape[axis]): illegal_value(upp, 'upp', 'selectImageBounds') @@ -1237,13 +1273,12 @@ print(f'lower bound = {low} (inclusive)\nupper bound = {upp} (exclusive)') quickImshow(a_tmp, title=title) del a_tmp - if pyip.inputYesNo('Accept these bounds ([y]/n)?: ', blank=True) == 'no': + if not input_yesno('Accept these bounds (y/n)?', 'y'): bounds = selectImageBounds(a, axis, low=low_save, upp=upp_save, num_min=num_min_save, title=title) return bounds - class Config: """Base class for processing a config file or dictionary. """ @@ -1283,7 +1318,7 @@ self.config = {item[0].strip():literal_eval(item[1].strip()) for item in [line.split('#')[0].split('=') for line in lines if '=' in line.split('#')[0]]} else: - illegal_value(self.suffix, 'config file extension', 'loadFile') + illegal_value(self.suffix, 'config file extension', 'Config.loadFile') # Make sure config file was correctly loaded if isinstance(self.config, dict): @@ -1295,7 +1330,6 @@ def loadDict(self, config_dict): """Takes a dictionary and places it into self.config. """ - exit('loadDict not tested yet, what format do we follow: txt or yaml?') if self.load_flag: logging.warning('Overwriting the previously loaded config file') @@ -1303,7 +1337,7 @@ self.config = config_dict self.load_flag = True else: - illegal_value(config_dict, 'dictionary config object', 'loadDict') + illegal_value(config_dict, 'dictionary config object', 'Config.loadDict') self.config = {} def saveFile(self, config_file): @@ -1311,7 +1345,7 @@ """ suffix = os.path.splitext(config_file)[1] if suffix != '.yml' and suffix != '.yaml': - illegal_value(suffix, 'config file extension', 'saveFile') + illegal_value(suffix, 'config file extension', 'Config.saveFile') # Check if config file exists if os.path.isfile(config_file): @@ -1324,15 +1358,31 @@ yaml.safe_dump(self.config, f) def validate(self, pars_required, pars_missing=None): - """Returns False if any required first level keys are missing. + """Returns False if any required keys are missing. """ if not self.load_flag: logging.error('Load a config file prior to calling Config.validate') - pars = [p for p in pars_required if p not in self.config] - if isinstance(pars_missing, list): - pars_missing.extend(pars) - elif pars_missing is not None: - illegal_value(pars_missing, 'pars_missing', 'Config.validate') - if len(pars) > 0: + + def validate_nested_pars(config, par): + par_levels = par.split(':') + first_level_par = par_levels[0] + try: + first_level_par = int(first_level_par) + except: + pass + try: + next_level_config = config[first_level_par] + if len(par_levels) > 1: + next_level_par = ':'.join(par_levels[1:]) + return validate_nested_pars(next_level_config, next_level_par) + else: + return True + except: + return False + + pars_missing = [p for p in pars_required if not validate_nested_pars(self.config, p)] + if len(pars_missing) > 0: + logging.error(f'Missing item(s) in configuration: {", ".join(pars_missing)}') return False - return True + else: + return True