Mercurial > repos > rv43 > tomo
diff general.py @ 63:98a83f03d91b draft
"planemo upload for repository https://github.com/rolfverberg/galaxytools commit a9ebbfca439737ae17ddcb0df84408155f964877"
author | rv43 |
---|---|
date | Thu, 18 Aug 2022 14:36:46 +0000 |
parents | ca61007a60fa |
children | f31ef7bfb430 |
line wrap: on
line diff
--- a/general.py Wed Aug 17 15:10:13 2022 +0000 +++ b/general.py Thu Aug 18 14:36:46 2022 +0000 @@ -23,6 +23,10 @@ from matplotlib.widgets import Button except: pass +try: + import pyinputplus as pyip +except: + pass from ast import literal_eval from copy import deepcopy @@ -288,7 +292,7 @@ else: return int(mo.group()) -def input_int(s=None, v_min=None, v_max=None, default=None, inset=None): +def input_int(s=None, v_min=None, v_max=None, default=None): if default is not None: if not isinstance(default, int): illegal_value(default, 'default', 'input_int') @@ -298,14 +302,14 @@ default_string = '' if v_min is not None: if not isinstance(v_min, int): - illegal_value(v_min, 'v_min', 'input_int') + illegal_value(vmin, 'vmin', '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(v_max, 'v_max', 'input_int') + illegal_value(vmax, 'vmax', '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})') @@ -313,13 +317,8 @@ 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' ({v_min}, {v_max})' + v_range = f' (in range [{v_min}, {v_max}])' elif v_min is not None: v_range = f' (>= {v_min})' elif v_max is not None: @@ -334,11 +333,8 @@ 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: @@ -375,7 +371,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' ({v_min}, {v_max})' + v_range = f' (in range [{v_min}, {v_max}])' elif v_min is not None: v_range = f' (>= {v_min})' elif v_max is not None: @@ -390,7 +386,6 @@ 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): @@ -415,7 +410,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 ({v_min}, {v_max}))' + v_range = f' (each value in range [{v_min}, {v_max}])' elif v_min is not None: v_range = f' (each value >= {v_min})' elif v_max is not None: @@ -462,55 +457,15 @@ i = input() if isinstance(i, str) and not len(i): i = default - print(f'{i}') - if i is not None and i.lower() in 'yes': + if i.lower() in 'yes': v = True - elif i is not None and i.lower() in 'no': + elif 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): @@ -537,11 +492,10 @@ return mask def draw_mask_1d(ydata, xdata=None, current_index_ranges=None, current_mask=None, - select_mask=True, num_index_ranges_max=None, title=None, legend=None, test_mode=False): + select_mask=True): def draw_selections(ax): ax.clear() - ax.set_title(title) - ax.legend([legend]) + ax.set_title(axes_title) ax.plot(xdata, ydata, 'k') for (low, upp) in current_include: xlow = 0.5*(xdata[max(0, low-1)]+xdata[low]) @@ -609,7 +563,7 @@ ydata = np.asarray(ydata) if ydata.ndim > 1: logging.warning(f'Illegal ydata dimension ({ydata.ndim})') - return None, None + return None num_data = ydata.size if xdata is None: xdata = np.arange(num_data) @@ -617,34 +571,24 @@ 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, None + return None if not np.all(xdata[:-1] < xdata[1:]): logging.warning('Illegal xdata: must be monotonically increasing') - return None, None + return 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, None + return None if not isinstance(select_mask, bool): logging.warning('Illegal select_mask parameter ({select_mask}, {type(select_mask)})') - 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 + return None if select_mask: - title = f'Click and drag to {title} you wish to include' + axes_title = 'Click and drag to select ranges of data you wish to include.' selection_color = 'green' else: - title = f'Click and drag to {title} you wish to exclude' + axes_title = 'Click and drag to select ranges of data you wish to exclude.' selection_color = 'red' # Set initial selected mask and the selected/unselected index ranges as needed @@ -691,32 +635,29 @@ if current_include[-1][1] < num_data-1: current_exclude.append((current_include[-1][1]+1, num_data-1)) - if not test_mode: + # Set up matplotlib figure + fig, ax = plt.subplots() + plt.subplots_adjust(bottom=0.2) + draw_selections(ax) - # 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 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(block=True) + # Show figure + plt.show() - # 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: @@ -785,19 +726,21 @@ else: name = ' ' # Check existing values - use_input = False + use_input = 'no' if (is_int(first_index, 0) and is_int(offset, 0) and is_int(num_imgs, 1)): if offset < 0: - use_input = input_yesno(f'\nCurrent{name}first index = {first_index}, '+ - 'use this value (y/n)?', 'y') + use_input = pyip.inputYesNo('\nCurrent'+name+f'first index = {first_index}, '+ + 'use this value ([y]/n)? ', blank=True) else: - use_input = input_yesno(f'\nCurrent{name}first index/offset = '+ - f'{first_index}/{offset}, use these values (y/n)?', 'y') + use_input = pyip.inputYesNo('\nCurrent'+name+'first index/offset = '+ + f'{first_index}/{offset}, use these values ([y]/n)? ', + blank=True) if num_required is None: - 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: + 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': return first_index, offset, num_imgs # Check range against requirements @@ -823,27 +766,32 @@ 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' - choice = input_menu([use_all, pick_offset, pick_bounds], default=pick_offset) - if not choice: + menuchoice = pyip.inputMenu([use_all, pick_offset, pick_bounds], numbered=True) + if menuchoice == use_all: offset = 0 - elif choice == 1: - offset = input_int('Enter the first index offset', 0, last_index-first_index) + 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) first_index += offset if first_index == last_index: num_imgs = 1 else: - num_imgs = input_int('Enter the number of images', 1, num_imgs-offset) + num_imgs = pyip.inputInt(f'Enter the number of images [1, {num_imgs-offset}]: ', + min=1, max=num_imgs-offset) else: - offset = input_int('Enter the first index', first_index, last_index) + offset = pyip.inputInt(f'Enter the first index [{first_index}, {last_index}]: ', + min=first_index, max=last_index)-first_index first_index += offset - num_imgs = input_int('Enter the last index', first_index, last_index)-first_index+1 + num_imgs = pyip.inputInt(f'Enter the last index [{first_index}, {last_index}]: ', + min=first_index, max=last_index)-first_index+1 else: use_all = f'Use ([{first_index}, {first_index+num_required-1}])' pick_offset = 'Pick the first index offset' - choice = input_menu([use_all, pick_offset], pick_offset) + menuchoice = pyip.inputMenu([use_all, pick_offset], numbered=True) offset = 0 - if choice == 1: - offset = input_int('Enter the first index offset', 0, num_imgs-num_required) + 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) first_index += offset num_imgs = num_required @@ -925,7 +873,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', 'loadImageStack') + illegal_value(filetype, 'filetype', 'findImageRange') return img_stack def combine_tiffs_in_h5(files, num_imgs, h5_filename): @@ -1136,8 +1084,8 @@ illegal_value(x_upp, 'x_upp', 'selectArrayBounds') return None quickPlot((range(len_a), a), vlines=(x_low,x_upp), title=title) - if not input_yesno(f'\nCurrent array bounds: [{x_low}, {x_upp}] '+ - 'use these values (y/n)?', 'y'): + if pyip.inputYesNo(f'\nCurrent array bounds: [{x_low}, {x_upp}], '+ + 'use these values ([y]/n)? ', blank=True) == 'no': x_low = None x_upp = None else: @@ -1150,13 +1098,17 @@ 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 = 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) + 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) + else: + x_low = pyip.inputInt(f' Set lower data bound [0, {x_low_max}]: ', + min=0, max=x_low_max) break - else: - 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') @@ -1167,20 +1119,24 @@ x_upp_min = x_min while True: quickPlot(range(x_min, x_max), a[x_min:x_max], title=title) - 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) + 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) + else: + x_upp = pyip.inputInt(f' Set upper data bound [{x_upp_min}, {len_a}]: ', + min=x_upp_min, max=len_a) break - else: - 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 not input_yesno('Accept these bounds (y/n)?', 'y'): + if pyip.inputYesNo('Accept these bounds ([y]/n)?: ', blank=True) == 'no': x_low, x_upp = selectArrayBounds(a, None, None, num_x_min, title=title) clearPlot(title) return x_low, x_upp @@ -1228,13 +1184,17 @@ else: quickImshow(a[min_:max_,:], title=title, aspect='auto', extent=[0,a.shape[1], max_,min_]) - zoom_flag = input_yesno('Set lower data bound (y) or zoom in (n)?', 'y') + zoom_flag = pyip.inputInt('Set lower data bound (0) or zoom in (1)?: ', + min=0, max=1) if zoom_flag: - low = input_int(' Set lower data bound', 0, low_max) + 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) + else: + low = pyip.inputInt(f' Set lower data bound [0, {low_max}]: ', + min=0, max=low_max) break - else: - 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') @@ -1250,13 +1210,17 @@ else: quickImshow(a[min_:max_,:], title=title, aspect='auto', extent=[0,a.shape[1], max_,min_]) - zoom_flag = input_yesno('Set upper data bound (y) or zoom in (n)?', 'y') + zoom_flag = pyip.inputInt('Set upper data bound (0) or zoom in (1)?: ', + min=0, max=1) if zoom_flag: - upp = input_int(' Set upper data bound', upp_min, a.shape[axis]) + 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]) + else: + upp = pyip.inputInt(f' Set upper data bound [{upp_min}, {a.shape[axis]}]: ', + min=upp_min, max=a.shape[axis]) break - else: - 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') @@ -1273,12 +1237,13 @@ print(f'lower bound = {low} (inclusive)\nupper bound = {upp} (exclusive)') quickImshow(a_tmp, title=title) del a_tmp - if not input_yesno('Accept these bounds (y/n)?', 'y'): + if pyip.inputYesNo('Accept these bounds ([y]/n)?: ', blank=True) == 'no': 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. """ @@ -1318,7 +1283,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', 'Config.loadFile') + illegal_value(self.suffix, 'config file extension', 'loadFile') # Make sure config file was correctly loaded if isinstance(self.config, dict): @@ -1330,6 +1295,7 @@ 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') @@ -1337,7 +1303,7 @@ self.config = config_dict self.load_flag = True else: - illegal_value(config_dict, 'dictionary config object', 'Config.loadDict') + illegal_value(config_dict, 'dictionary config object', 'loadDict') self.config = {} def saveFile(self, config_file): @@ -1345,7 +1311,7 @@ """ suffix = os.path.splitext(config_file)[1] if suffix != '.yml' and suffix != '.yaml': - illegal_value(suffix, 'config file extension', 'Config.saveFile') + illegal_value(suffix, 'config file extension', 'saveFile') # Check if config file exists if os.path.isfile(config_file): @@ -1358,31 +1324,15 @@ yaml.safe_dump(self.config, f) def validate(self, pars_required, pars_missing=None): - """Returns False if any required keys are missing. + """Returns False if any required first level keys are missing. """ if not self.load_flag: logging.error('Load a config file prior to calling Config.validate') - - 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)}') + 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: return False - else: - return True + return True