comparison 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
comparison
equal deleted inserted replaced
64:15288e9746e0 65:f31ef7bfb430
19 pass 19 pass
20 import numpy as np 20 import numpy as np
21 try: 21 try:
22 import matplotlib.pyplot as plt 22 import matplotlib.pyplot as plt
23 from matplotlib.widgets import Button 23 from matplotlib.widgets import Button
24 except:
25 pass
26 try:
27 import pyinputplus as pyip
28 except: 24 except:
29 pass 25 pass
30 26
31 from ast import literal_eval 27 from ast import literal_eval
32 from copy import deepcopy 28 from copy import deepcopy
290 if mo is None: 286 if mo is None:
291 return None 287 return None
292 else: 288 else:
293 return int(mo.group()) 289 return int(mo.group())
294 290
295 def input_int(s=None, v_min=None, v_max=None, default=None): 291 def input_int(s=None, v_min=None, v_max=None, default=None, inset=None):
296 if default is not None: 292 if default is not None:
297 if not isinstance(default, int): 293 if not isinstance(default, int):
298 illegal_value(default, 'default', 'input_int') 294 illegal_value(default, 'default', 'input_int')
299 return None 295 return None
300 default_string = f' [{default}]' 296 default_string = f' [{default}]'
301 else: 297 else:
302 default_string = '' 298 default_string = ''
303 if v_min is not None: 299 if v_min is not None:
304 if not isinstance(v_min, int): 300 if not isinstance(v_min, int):
305 illegal_value(vmin, 'vmin', 'input_int') 301 illegal_value(v_min, 'v_min', 'input_int')
306 return None 302 return None
307 if default is not None and default < v_min: 303 if default is not None and default < v_min:
308 logging.error('Illegal v_min, default combination ({v_min}, {default})') 304 logging.error('Illegal v_min, default combination ({v_min}, {default})')
309 return None 305 return None
310 if v_max is not None: 306 if v_max is not None:
311 if not isinstance(v_max, int): 307 if not isinstance(v_max, int):
312 illegal_value(vmax, 'vmax', 'input_int') 308 illegal_value(v_max, 'v_max', 'input_int')
313 return None 309 return None
314 if v_min is not None and v_min > v_max: 310 if v_min is not None and v_min > v_max:
315 logging.error(f'Illegal v_min, v_max combination ({v_min}, {v_max})') 311 logging.error(f'Illegal v_min, v_max combination ({v_min}, {v_max})')
316 return None 312 return None
317 if default is not None and default > v_max: 313 if default is not None and default > v_max:
318 logging.error('Illegal default, v_max combination ({default}, {v_max})') 314 logging.error('Illegal default, v_max combination ({default}, {v_max})')
319 return None 315 return None
316 if inset is not None:
317 if (not isinstance(inset, (tuple, list)) or False in [True if isinstance(i, int) else
318 False for i in inset]):
319 illegal_value(inset, 'inset', 'input_int')
320 return None
320 if v_min is not None and v_max is not None: 321 if v_min is not None and v_max is not None:
321 v_range = f' (in range [{v_min}, {v_max}])' 322 v_range = f' ({v_min}, {v_max})'
322 elif v_min is not None: 323 elif v_min is not None:
323 v_range = f' (>= {v_min})' 324 v_range = f' (>= {v_min})'
324 elif v_max is not None: 325 elif v_max is not None:
325 v_range = f' (<= {v_max})' 326 v_range = f' (<= {v_max})'
326 else: 327 else:
331 print(f'{s}{v_range}{default_string}: ') 332 print(f'{s}{v_range}{default_string}: ')
332 try: 333 try:
333 i = input() 334 i = input()
334 if isinstance(i, str) and not len(i): 335 if isinstance(i, str) and not len(i):
335 v = default 336 v = default
337 print(f'{v}')
336 else: 338 else:
337 v = literal_eval(i) 339 v = literal_eval(i)
340 if inset and v not in inset:
341 raise ValueError(f'{v} not part of the set {inset}')
338 except (ValueError, TypeError, SyntaxError, MemoryError, RecursionError): 342 except (ValueError, TypeError, SyntaxError, MemoryError, RecursionError):
339 v = None 343 v = None
340 except: 344 except:
341 print('Unexpected error') 345 print('Unexpected error')
342 raise 346 raise
369 return None 373 return None
370 if default is not None and default > v_max: 374 if default is not None and default > v_max:
371 logging.error('Illegal default, v_max combination ({default}, {v_max})') 375 logging.error('Illegal default, v_max combination ({default}, {v_max})')
372 return None 376 return None
373 if v_min is not None and v_max is not None: 377 if v_min is not None and v_max is not None:
374 v_range = f' (in range [{v_min}, {v_max}])' 378 v_range = f' ({v_min}, {v_max})'
375 elif v_min is not None: 379 elif v_min is not None:
376 v_range = f' (>= {v_min})' 380 v_range = f' (>= {v_min})'
377 elif v_max is not None: 381 elif v_max is not None:
378 v_range = f' (<= {v_max})' 382 v_range = f' (<= {v_max})'
379 else: 383 else:
384 print(f'{s}{v_range}{default_string}: ') 388 print(f'{s}{v_range}{default_string}: ')
385 try: 389 try:
386 i = input() 390 i = input()
387 if isinstance(i, str) and not len(i): 391 if isinstance(i, str) and not len(i):
388 v = default 392 v = default
393 print(f'{v}')
389 else: 394 else:
390 v = literal_eval(i) 395 v = literal_eval(i)
391 except (ValueError, TypeError, SyntaxError, MemoryError, RecursionError): 396 except (ValueError, TypeError, SyntaxError, MemoryError, RecursionError):
392 v = None 397 v = None
393 except: 398 except:
408 return None 413 return None
409 if v_max < v_min: 414 if v_max < v_min:
410 logging.error(f'Illegal v_min, v_max combination ({v_min}, {v_max})') 415 logging.error(f'Illegal v_min, v_max combination ({v_min}, {v_max})')
411 return None 416 return None
412 if v_min is not None and v_max is not None: 417 if v_min is not None and v_max is not None:
413 v_range = f' (each value in range [{v_min}, {v_max}])' 418 v_range = f' (each value in ({v_min}, {v_max}))'
414 elif v_min is not None: 419 elif v_min is not None:
415 v_range = f' (each value >= {v_min})' 420 v_range = f' (each value >= {v_min})'
416 elif v_max is not None: 421 elif v_max is not None:
417 v_range = f' (each value <= {v_max})' 422 v_range = f' (each value <= {v_max})'
418 else: 423 else:
455 else: 460 else:
456 print(f'{s}{default_string}: ') 461 print(f'{s}{default_string}: ')
457 i = input() 462 i = input()
458 if isinstance(i, str) and not len(i): 463 if isinstance(i, str) and not len(i):
459 i = default 464 i = default
460 if i.lower() in 'yes': 465 print(f'{i}')
466 if i is not None and i.lower() in 'yes':
461 v = True 467 v = True
462 elif i.lower() in 'no': 468 elif i is not None and i.lower() in 'no':
463 v = False 469 v = False
464 else: 470 else:
465 print('Illegal input, enter yes or no') 471 print('Illegal input, enter yes or no')
466 v = input_yesno(s, default) 472 v = input_yesno(s, default)
467 return v 473 return v
474
475 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
477 for i in items]:
478 illegal_value(items, 'items', 'input_menu')
479 return None
480 if default is not None:
481 if not (isinstance(default, str) and default in items):
482 logging.error(f'Illegal value for default ({default}), must be in {items}')
483 return None
484 default_string = f' [{items.index(default)+1}]'
485 else:
486 default_string = ''
487 if header is None:
488 print(f'Choose one of the following items (1, {len(items)}){default_string}:')
489 else:
490 print(f'{header} (1, {len(items)}){default_string}:')
491 for i, choice in enumerate(items):
492 print(f' {i+1}: {choice}')
493 try:
494 choice = input()
495 if isinstance(choice, str) and not len(choice):
496 choice = items.index(default)
497 print(f'{choice+1}')
498 else:
499 choice = literal_eval(choice)
500 if isinstance(choice, int) and 1 <= choice <= len(items):
501 choice -= 1
502 else:
503 raise ValueError
504 except (ValueError, TypeError, SyntaxError, MemoryError, RecursionError):
505 choice = None
506 except:
507 print('Unexpected error')
508 raise
509 if choice is None:
510 print(f'Illegal choice, enter a number between 1 and {len(items)}')
511 choice = input_menu(items, default)
512 return choice
468 513
469 def create_mask(x, bounds=None, reverse_mask=False, current_mask=None): 514 def create_mask(x, bounds=None, reverse_mask=False, current_mask=None):
470 # bounds is a pair of number in the same units a x 515 # bounds is a pair of number in the same units a x
471 if not isinstance(x, (tuple, list, np.ndarray)) or not len(x): 516 if not isinstance(x, (tuple, list, np.ndarray)) or not len(x):
472 logging.warning(f'Illegal input array ({x}, {type(x)})') 517 logging.warning(f'Illegal input array ({x}, {type(x)})')
490 if not True in mask: 535 if not True in mask:
491 logging.warning('Entire data array is masked') 536 logging.warning('Entire data array is masked')
492 return mask 537 return mask
493 538
494 def draw_mask_1d(ydata, xdata=None, current_index_ranges=None, current_mask=None, 539 def draw_mask_1d(ydata, xdata=None, current_index_ranges=None, current_mask=None,
495 select_mask=True): 540 select_mask=True, num_index_ranges_max=None, title=None, legend=None, test_mode=False):
496 def draw_selections(ax): 541 def draw_selections(ax):
497 ax.clear() 542 ax.clear()
498 ax.set_title(axes_title) 543 ax.set_title(title)
544 ax.legend([legend])
499 ax.plot(xdata, ydata, 'k') 545 ax.plot(xdata, ydata, 'k')
500 for (low, upp) in current_include: 546 for (low, upp) in current_include:
501 xlow = 0.5*(xdata[max(0, low-1)]+xdata[low]) 547 xlow = 0.5*(xdata[max(0, low-1)]+xdata[low])
502 xupp = 0.5*(xdata[upp]+xdata[min(num_data-1, upp+1)]) 548 xupp = 0.5*(xdata[upp]+xdata[min(num_data-1, upp+1)])
503 ax.axvspan(xlow, xupp, facecolor='green', alpha=0.5) 549 ax.axvspan(xlow, xupp, facecolor='green', alpha=0.5)
561 607
562 # Check for valid inputs 608 # Check for valid inputs
563 ydata = np.asarray(ydata) 609 ydata = np.asarray(ydata)
564 if ydata.ndim > 1: 610 if ydata.ndim > 1:
565 logging.warning(f'Illegal ydata dimension ({ydata.ndim})') 611 logging.warning(f'Illegal ydata dimension ({ydata.ndim})')
566 return None 612 return None, None
567 num_data = ydata.size 613 num_data = ydata.size
568 if xdata is None: 614 if xdata is None:
569 xdata = np.arange(num_data) 615 xdata = np.arange(num_data)
570 else: 616 else:
571 xdata = np.asarray(xdata, dtype=np.float64) 617 xdata = np.asarray(xdata, dtype=np.float64)
572 if xdata.ndim > 1 or xdata.size != num_data: 618 if xdata.ndim > 1 or xdata.size != num_data:
573 logging.warning(f'Illegal xdata shape ({xdata.shape})') 619 logging.warning(f'Illegal xdata shape ({xdata.shape})')
574 return None 620 return None, None
575 if not np.all(xdata[:-1] < xdata[1:]): 621 if not np.all(xdata[:-1] < xdata[1:]):
576 logging.warning('Illegal xdata: must be monotonically increasing') 622 logging.warning('Illegal xdata: must be monotonically increasing')
577 return None 623 return None, None
578 if current_index_ranges is not None: 624 if current_index_ranges is not None:
579 if not isinstance(current_index_ranges, (tuple, list)): 625 if not isinstance(current_index_ranges, (tuple, list)):
580 logging.warning('Illegal current_index_ranges parameter ({current_index_ranges}, '+ 626 logging.warning('Illegal current_index_ranges parameter ({current_index_ranges}, '+
581 f'{type(current_index_ranges)})') 627 f'{type(current_index_ranges)})')
582 return None 628 return None, None
583 if not isinstance(select_mask, bool): 629 if not isinstance(select_mask, bool):
584 logging.warning('Illegal select_mask parameter ({select_mask}, {type(select_mask)})') 630 logging.warning('Illegal select_mask parameter ({select_mask}, {type(select_mask)})')
585 return None 631 return None, None
632 if num_index_ranges_max is not None:
633 logging.warning('num_index_ranges_max input not yet implemented in draw_mask_1d')
634 if title is None:
635 title = 'select ranges of data'
636 elif not isinstance(title, str):
637 illegal(title, 'title')
638 title = ''
639 if legend is None and not isinstance(title, str):
640 illegal(legend, 'legend')
641 legend = None
586 642
587 if select_mask: 643 if select_mask:
588 axes_title = 'Click and drag to select ranges of data you wish to include.' 644 title = f'Click and drag to {title} you wish to include'
589 selection_color = 'green' 645 selection_color = 'green'
590 else: 646 else:
591 axes_title = 'Click and drag to select ranges of data you wish to exclude.' 647 title = f'Click and drag to {title} you wish to exclude'
592 selection_color = 'red' 648 selection_color = 'red'
593 649
594 # Set initial selected mask and the selected/unselected index ranges as needed 650 # Set initial selected mask and the selected/unselected index ranges as needed
595 selected_index_ranges = [] 651 selected_index_ranges = []
596 unselected_index_ranges = [] 652 unselected_index_ranges = []
633 for i in range(1, len(current_include)): 689 for i in range(1, len(current_include)):
634 current_exclude.append((current_include[i-1][1]+1, current_include[i][0]-1)) 690 current_exclude.append((current_include[i-1][1]+1, current_include[i][0]-1))
635 if current_include[-1][1] < num_data-1: 691 if current_include[-1][1] < num_data-1:
636 current_exclude.append((current_include[-1][1]+1, num_data-1)) 692 current_exclude.append((current_include[-1][1]+1, num_data-1))
637 693
638 # Set up matplotlib figure 694 if not test_mode:
639 fig, ax = plt.subplots() 695
640 plt.subplots_adjust(bottom=0.2) 696 # Set up matplotlib figure
641 draw_selections(ax) 697 plt.close('all')
642 698 fig, ax = plt.subplots()
643 # Set up event handling for click-and-drag range selection 699 plt.subplots_adjust(bottom=0.2)
644 cid_click = fig.canvas.mpl_connect('button_press_event', onclick) 700 draw_selections(ax)
645 cid_release = fig.canvas.mpl_connect('button_release_event', onrelease) 701
646 702 # Set up event handling for click-and-drag range selection
647 # Set up confirm / clear range selection buttons 703 cid_click = fig.canvas.mpl_connect('button_press_event', onclick)
648 confirm_b = Button(plt.axes([0.75, 0.05, 0.15, 0.075]), 'Confirm') 704 cid_release = fig.canvas.mpl_connect('button_release_event', onrelease)
649 clear_b = Button(plt.axes([0.59, 0.05, 0.15, 0.075]), 'Clear') 705
650 cid_confirm = confirm_b.on_clicked(confirm_selection) 706 # Set up confirm / clear range selection buttons
651 cid_clear = clear_b.on_clicked(clear_last_selection) 707 confirm_b = Button(plt.axes([0.75, 0.05, 0.15, 0.075]), 'Confirm')
652 708 clear_b = Button(plt.axes([0.59, 0.05, 0.15, 0.075]), 'Clear')
653 # Show figure 709 cid_confirm = confirm_b.on_clicked(confirm_selection)
654 plt.show() 710 cid_clear = clear_b.on_clicked(clear_last_selection)
655 711
656 # Disconnect callbacks when figure is closed 712 # Show figure
657 fig.canvas.mpl_disconnect(cid_click) 713 plt.show(block=True)
658 fig.canvas.mpl_disconnect(cid_release) 714
659 confirm_b.disconnect(cid_confirm) 715 # Disconnect callbacks when figure is closed
660 clear_b.disconnect(cid_clear) 716 fig.canvas.mpl_disconnect(cid_click)
717 fig.canvas.mpl_disconnect(cid_release)
718 confirm_b.disconnect(cid_confirm)
719 clear_b.disconnect(cid_clear)
661 720
662 # Swap selection depending on select_mask 721 # Swap selection depending on select_mask
663 if not select_mask: 722 if not select_mask:
664 selected_index_ranges, unselected_index_ranges = unselected_index_ranges, \ 723 selected_index_ranges, unselected_index_ranges = unselected_index_ranges, \
665 selected_index_ranges 724 selected_index_ranges
724 if isinstance(name, str): 783 if isinstance(name, str):
725 name = f' {name} ' 784 name = f' {name} '
726 else: 785 else:
727 name = ' ' 786 name = ' '
728 # Check existing values 787 # Check existing values
729 use_input = 'no' 788 use_input = False
730 if (is_int(first_index, 0) and is_int(offset, 0) and is_int(num_imgs, 1)): 789 if (is_int(first_index, 0) and is_int(offset, 0) and is_int(num_imgs, 1)):
731 if offset < 0: 790 if offset < 0:
732 use_input = pyip.inputYesNo('\nCurrent'+name+f'first index = {first_index}, '+ 791 use_input = input_yesno(f'\nCurrent{name}first index = {first_index}, '+
733 'use this value ([y]/n)? ', blank=True) 792 'use this value (y/n)?', 'y')
734 else: 793 else:
735 use_input = pyip.inputYesNo('\nCurrent'+name+'first index/offset = '+ 794 use_input = input_yesno(f'\nCurrent{name}first index/offset = '+
736 f'{first_index}/{offset}, use these values ([y]/n)? ', 795 f'{first_index}/{offset}, use these values (y/n)?', 'y')
737 blank=True)
738 if num_required is None: 796 if num_required is None:
739 if use_input != 'no': 797 if use_input:
740 use_input = pyip.inputYesNo('Current number of'+name+'images = '+ 798 use_input = input_yesno(f'Current number of{name}images = '+
741 f'{num_imgs}, use this value ([y]/n)? ', 799 f'{num_imgs}, use this value (y/n)? ', 'y')
742 blank=True) 800 if use_input:
743 if use_input != 'no':
744 return first_index, offset, num_imgs 801 return first_index, offset, num_imgs
745 802
746 # Check range against requirements 803 # Check range against requirements
747 if num_imgs < 1: 804 if num_imgs < 1:
748 logging.warning('No available'+name+'images') 805 logging.warning('No available'+name+'images')
764 if num_required is None: 821 if num_required is None:
765 last_index = first_index+num_imgs 822 last_index = first_index+num_imgs
766 use_all = f'Use all ([{first_index}, {last_index}])' 823 use_all = f'Use all ([{first_index}, {last_index}])'
767 pick_offset = 'Pick a first index offset and a number of images' 824 pick_offset = 'Pick a first index offset and a number of images'
768 pick_bounds = 'Pick the first and last index' 825 pick_bounds = 'Pick the first and last index'
769 menuchoice = pyip.inputMenu([use_all, pick_offset, pick_bounds], numbered=True) 826 choice = input_menu([use_all, pick_offset, pick_bounds], default=pick_offset)
770 if menuchoice == use_all: 827 if not choice:
771 offset = 0 828 offset = 0
772 elif menuchoice == pick_offset: 829 elif choice == 1:
773 offset = pyip.inputInt('Enter the first index offset'+ 830 offset = input_int('Enter the first index offset', 0, last_index-first_index)
774 f' [0, {last_index-first_index}]: ', min=0, max=last_index-first_index)
775 first_index += offset 831 first_index += offset
776 if first_index == last_index: 832 if first_index == last_index:
777 num_imgs = 1 833 num_imgs = 1
778 else: 834 else:
779 num_imgs = pyip.inputInt(f'Enter the number of images [1, {num_imgs-offset}]: ', 835 num_imgs = input_int('Enter the number of images', 1, num_imgs-offset)
780 min=1, max=num_imgs-offset) 836 else:
781 else: 837 offset = input_int('Enter the first index', first_index, last_index)
782 offset = pyip.inputInt(f'Enter the first index [{first_index}, {last_index}]: ',
783 min=first_index, max=last_index)-first_index
784 first_index += offset 838 first_index += offset
785 num_imgs = pyip.inputInt(f'Enter the last index [{first_index}, {last_index}]: ', 839 num_imgs = input_int('Enter the last index', first_index, last_index)-first_index+1
786 min=first_index, max=last_index)-first_index+1
787 else: 840 else:
788 use_all = f'Use ([{first_index}, {first_index+num_required-1}])' 841 use_all = f'Use ([{first_index}, {first_index+num_required-1}])'
789 pick_offset = 'Pick the first index offset' 842 pick_offset = 'Pick the first index offset'
790 menuchoice = pyip.inputMenu([use_all, pick_offset], numbered=True) 843 choice = input_menu([use_all, pick_offset], pick_offset)
791 offset = 0 844 offset = 0
792 if menuchoice == pick_offset: 845 if choice == 1:
793 offset = pyip.inputInt('Enter the first index offset'+ 846 offset = input_int('Enter the first index offset', 0, num_imgs-num_required)
794 f'[0, {num_imgs-num_required}]: ', min=0, max=num_imgs-num_required)
795 first_index += offset 847 first_index += offset
796 num_imgs = num_required 848 num_imgs = num_required
797 849
798 return first_index, offset, num_imgs 850 return first_index, offset, num_imgs
799 851
871 img_stack = f.get('entry/instrument/detector/data')[ 923 img_stack = f.get('entry/instrument/detector/data')[
872 img_offset:img_offset+num_imgs:num_img_skip+1, 924 img_offset:img_offset+num_imgs:num_img_skip+1,
873 img_x_bounds[0]:img_x_bounds[1],img_y_bounds[0]:img_y_bounds[1]] 925 img_x_bounds[0]:img_x_bounds[1],img_y_bounds[0]:img_y_bounds[1]]
874 logging.info(f'... done in {time()-t0:.2f} seconds!') 926 logging.info(f'... done in {time()-t0:.2f} seconds!')
875 else: 927 else:
876 illegal_value(filetype, 'filetype', 'findImageRange') 928 illegal_value(filetype, 'filetype', 'loadImageStack')
877 return img_stack 929 return img_stack
878 930
879 def combine_tiffs_in_h5(files, num_imgs, h5_filename): 931 def combine_tiffs_in_h5(files, num_imgs, h5_filename):
880 img_stack = loadImageStack(files, 'tif', 0, num_imgs) 932 img_stack = loadImageStack(files, 'tif', 0, num_imgs)
881 with h5py.File(h5_filename, 'w') as f: 933 with h5py.File(h5_filename, 'w') as f:
1082 x_upp = len_a 1134 x_upp = len_a
1083 if not is_int(x_upp, x_low+num_x_min, len_a): 1135 if not is_int(x_upp, x_low+num_x_min, len_a):
1084 illegal_value(x_upp, 'x_upp', 'selectArrayBounds') 1136 illegal_value(x_upp, 'x_upp', 'selectArrayBounds')
1085 return None 1137 return None
1086 quickPlot((range(len_a), a), vlines=(x_low,x_upp), title=title) 1138 quickPlot((range(len_a), a), vlines=(x_low,x_upp), title=title)
1087 if pyip.inputYesNo(f'\nCurrent array bounds: [{x_low}, {x_upp}], '+ 1139 if not input_yesno(f'\nCurrent array bounds: [{x_low}, {x_upp}] '+
1088 'use these values ([y]/n)? ', blank=True) == 'no': 1140 'use these values (y/n)?', 'y'):
1089 x_low = None 1141 x_low = None
1090 x_upp = None 1142 x_upp = None
1091 else: 1143 else:
1092 clearPlot(title) 1144 clearPlot(title)
1093 return x_low, x_upp 1145 return x_low, x_upp
1096 x_min = 0 1148 x_min = 0
1097 x_max = len_a 1149 x_max = len_a
1098 x_low_max = len_a-num_x_min 1150 x_low_max = len_a-num_x_min
1099 while True: 1151 while True:
1100 quickPlot(range(x_min, x_max), a[x_min:x_max], title=title) 1152 quickPlot(range(x_min, x_max), a[x_min:x_max], title=title)
1101 zoom_flag = pyip.inputInt('Set lower data bound ([0]) or zoom in (1)?: ', 1153 zoom_flag = input_yesno('Set lower data bound (y) or zoom in (n)?', 'y')
1102 min=0, max=1, blank=True) 1154 if zoom_flag:
1103 if zoom_flag == 1: 1155 x_low = input_int(' Set lower data bound', 0, x_low_max)
1104 x_min = pyip.inputInt(f' Set lower zoom index [0, {x_low_max}]: ', 1156 break
1105 min=0, max=x_low_max)
1106 x_max = pyip.inputInt(f' Set upper zoom index [{x_min+1}, {x_low_max+1}]: ',
1107 min=x_min+1, max=x_low_max+1)
1108 else: 1157 else:
1109 x_low = pyip.inputInt(f' Set lower data bound [0, {x_low_max}]: ', 1158 x_min = input_int(' Set lower zoom index', 0, x_low_max)
1110 min=0, max=x_low_max) 1159 x_max = input_int(' Set upper zoom index', x_min+1, x_low_max+1)
1111 break
1112 else: 1160 else:
1113 if not is_int(x_low, 0, len_a-num_x_min): 1161 if not is_int(x_low, 0, len_a-num_x_min):
1114 illegal_value(x_low, 'x_low', 'selectArrayBounds') 1162 illegal_value(x_low, 'x_low', 'selectArrayBounds')
1115 return None 1163 return None
1116 if x_upp is None: 1164 if x_upp is None:
1117 x_min = x_low+num_x_min 1165 x_min = x_low+num_x_min
1118 x_max = len_a 1166 x_max = len_a
1119 x_upp_min = x_min 1167 x_upp_min = x_min
1120 while True: 1168 while True:
1121 quickPlot(range(x_min, x_max), a[x_min:x_max], title=title) 1169 quickPlot(range(x_min, x_max), a[x_min:x_max], title=title)
1122 zoom_flag = pyip.inputInt('Set upper data bound ([0]) or zoom in (1)?: ', 1170 zoom_flag = input_yesno('Set upper data bound (y) or zoom in (n)?', 'y')
1123 min=0, max=1, blank=True) 1171 if zoom_flag:
1124 if zoom_flag == 1: 1172 x_upp = input_int(' Set upper data bound', x_upp_min, len_a)
1125 x_min = pyip.inputInt(f' Set upper zoom index [{x_upp_min}, {len_a-1}]: ', 1173 break
1126 min=x_upp_min, max=len_a-1)
1127 x_max = pyip.inputInt(f' Set upper zoom index [{x_min+1}, {len_a}]: ',
1128 min=x_min+1, max=len_a)
1129 else: 1174 else:
1130 x_upp = pyip.inputInt(f' Set upper data bound [{x_upp_min}, {len_a}]: ', 1175 x_min = input_int(' Set upper zoom index', x_upp_min, len_a-1)
1131 min=x_upp_min, max=len_a) 1176 x_max = input_int(' Set upper zoom index', x_min+1, len_a)
1132 break
1133 else: 1177 else:
1134 if not is_int(x_upp, x_low+num_x_min, len_a): 1178 if not is_int(x_upp, x_low+num_x_min, len_a):
1135 illegal_value(x_upp, 'x_upp', 'selectArrayBounds') 1179 illegal_value(x_upp, 'x_upp', 'selectArrayBounds')
1136 return None 1180 return None
1137 print(f'lower bound = {x_low} (inclusive)\nupper bound = {x_upp} (exclusive)]') 1181 print(f'lower bound = {x_low} (inclusive)\nupper bound = {x_upp} (exclusive)]')
1138 quickPlot((range(len_a), a), vlines=(x_low,x_upp), title=title) 1182 quickPlot((range(len_a), a), vlines=(x_low,x_upp), title=title)
1139 if pyip.inputYesNo('Accept these bounds ([y]/n)?: ', blank=True) == 'no': 1183 if not input_yesno('Accept these bounds (y/n)?', 'y'):
1140 x_low, x_upp = selectArrayBounds(a, None, None, num_x_min, title=title) 1184 x_low, x_upp = selectArrayBounds(a, None, None, num_x_min, title=title)
1141 clearPlot(title) 1185 clearPlot(title)
1142 return x_low, x_upp 1186 return x_low, x_upp
1143 1187
1144 def selectImageBounds(a, axis, low=None, upp=None, num_min=None, 1188 def selectImageBounds(a, axis, low=None, upp=None, num_min=None,
1182 quickImshow(a[:,min_:max_], title=title, aspect='auto', 1226 quickImshow(a[:,min_:max_], title=title, aspect='auto',
1183 extent=[min_,max_,a.shape[0],0]) 1227 extent=[min_,max_,a.shape[0],0])
1184 else: 1228 else:
1185 quickImshow(a[min_:max_,:], title=title, aspect='auto', 1229 quickImshow(a[min_:max_,:], title=title, aspect='auto',
1186 extent=[0,a.shape[1], max_,min_]) 1230 extent=[0,a.shape[1], max_,min_])
1187 zoom_flag = pyip.inputInt('Set lower data bound (0) or zoom in (1)?: ', 1231 zoom_flag = input_yesno('Set lower data bound (y) or zoom in (n)?', 'y')
1188 min=0, max=1)
1189 if zoom_flag: 1232 if zoom_flag:
1190 min_ = pyip.inputInt(f' Set lower zoom index [0, {low_max}]: ', 1233 low = input_int(' Set lower data bound', 0, low_max)
1191 min=0, max=low_max) 1234 break
1192 max_ = pyip.inputInt(f' Set upper zoom index [{min_+1}, {low_max+1}]: ',
1193 min=min_+1, max=low_max+1)
1194 else: 1235 else:
1195 low = pyip.inputInt(f' Set lower data bound [0, {low_max}]: ', 1236 min_ = input_int(' Set lower zoom index', 0, low_max)
1196 min=0, max=low_max) 1237 max_ = input_int(' Set upper zoom index', min_+1, low_max+1)
1197 break
1198 else: 1238 else:
1199 if not is_int(low, 0, a.shape[axis]-num_min): 1239 if not is_int(low, 0, a.shape[axis]-num_min):
1200 illegal_value(low, 'low', 'selectImageBounds') 1240 illegal_value(low, 'low', 'selectImageBounds')
1201 return None 1241 return None
1202 if upp is None: 1242 if upp is None:
1208 quickImshow(a[:,min_:max_], title=title, aspect='auto', 1248 quickImshow(a[:,min_:max_], title=title, aspect='auto',
1209 extent=[min_,max_,a.shape[0],0]) 1249 extent=[min_,max_,a.shape[0],0])
1210 else: 1250 else:
1211 quickImshow(a[min_:max_,:], title=title, aspect='auto', 1251 quickImshow(a[min_:max_,:], title=title, aspect='auto',
1212 extent=[0,a.shape[1], max_,min_]) 1252 extent=[0,a.shape[1], max_,min_])
1213 zoom_flag = pyip.inputInt('Set upper data bound (0) or zoom in (1)?: ', 1253 zoom_flag = input_yesno('Set upper data bound (y) or zoom in (n)?', 'y')
1214 min=0, max=1)
1215 if zoom_flag: 1254 if zoom_flag:
1216 min_ = pyip.inputInt(f' Set upper zoom index [{upp_min}, {a.shape[axis]-1}]: ', 1255 upp = input_int(' Set upper data bound', upp_min, a.shape[axis])
1217 min=upp_min, max=a.shape[axis]-1) 1256 break
1218 max_ = pyip.inputInt(f' Set upper zoom index [{min_+1}, {a.shape[axis]}]: ',
1219 min=min_+1, max=a.shape[axis])
1220 else: 1257 else:
1221 upp = pyip.inputInt(f' Set upper data bound [{upp_min}, {a.shape[axis]}]: ', 1258 min_ = input_int(' Set upper zoom index', upp_min, a.shape[axis]-1)
1222 min=upp_min, max=a.shape[axis]) 1259 max_ = input_int(' Set upper zoom index', min_+1, a.shape[axis])
1223 break
1224 else: 1260 else:
1225 if not is_int(upp, low+num_min, a.shape[axis]): 1261 if not is_int(upp, low+num_min, a.shape[axis]):
1226 illegal_value(upp, 'upp', 'selectImageBounds') 1262 illegal_value(upp, 'upp', 'selectImageBounds')
1227 return None 1263 return None
1228 bounds = (low, upp) 1264 bounds = (low, upp)
1235 a_tmp[bounds[0],:] = a_tmp_max 1271 a_tmp[bounds[0],:] = a_tmp_max
1236 a_tmp[bounds[1]-1,:] = a_tmp_max 1272 a_tmp[bounds[1]-1,:] = a_tmp_max
1237 print(f'lower bound = {low} (inclusive)\nupper bound = {upp} (exclusive)') 1273 print(f'lower bound = {low} (inclusive)\nupper bound = {upp} (exclusive)')
1238 quickImshow(a_tmp, title=title) 1274 quickImshow(a_tmp, title=title)
1239 del a_tmp 1275 del a_tmp
1240 if pyip.inputYesNo('Accept these bounds ([y]/n)?: ', blank=True) == 'no': 1276 if not input_yesno('Accept these bounds (y/n)?', 'y'):
1241 bounds = selectImageBounds(a, axis, low=low_save, upp=upp_save, num_min=num_min_save, 1277 bounds = selectImageBounds(a, axis, low=low_save, upp=upp_save, num_min=num_min_save,
1242 title=title) 1278 title=title)
1243 return bounds 1279 return bounds
1244
1245 1280
1246 1281
1247 class Config: 1282 class Config:
1248 """Base class for processing a config file or dictionary. 1283 """Base class for processing a config file or dictionary.
1249 """ 1284 """
1281 with open(config_file, 'r') as f: 1316 with open(config_file, 'r') as f:
1282 lines = f.read().splitlines() 1317 lines = f.read().splitlines()
1283 self.config = {item[0].strip():literal_eval(item[1].strip()) for item in 1318 self.config = {item[0].strip():literal_eval(item[1].strip()) for item in
1284 [line.split('#')[0].split('=') for line in lines if '=' in line.split('#')[0]]} 1319 [line.split('#')[0].split('=') for line in lines if '=' in line.split('#')[0]]}
1285 else: 1320 else:
1286 illegal_value(self.suffix, 'config file extension', 'loadFile') 1321 illegal_value(self.suffix, 'config file extension', 'Config.loadFile')
1287 1322
1288 # Make sure config file was correctly loaded 1323 # Make sure config file was correctly loaded
1289 if isinstance(self.config, dict): 1324 if isinstance(self.config, dict):
1290 self.load_flag = True 1325 self.load_flag = True
1291 else: 1326 else:
1293 self.config = {} 1328 self.config = {}
1294 1329
1295 def loadDict(self, config_dict): 1330 def loadDict(self, config_dict):
1296 """Takes a dictionary and places it into self.config. 1331 """Takes a dictionary and places it into self.config.
1297 """ 1332 """
1298 exit('loadDict not tested yet, what format do we follow: txt or yaml?')
1299 if self.load_flag: 1333 if self.load_flag:
1300 logging.warning('Overwriting the previously loaded config file') 1334 logging.warning('Overwriting the previously loaded config file')
1301 1335
1302 if isinstance(config_dict, dict): 1336 if isinstance(config_dict, dict):
1303 self.config = config_dict 1337 self.config = config_dict
1304 self.load_flag = True 1338 self.load_flag = True
1305 else: 1339 else:
1306 illegal_value(config_dict, 'dictionary config object', 'loadDict') 1340 illegal_value(config_dict, 'dictionary config object', 'Config.loadDict')
1307 self.config = {} 1341 self.config = {}
1308 1342
1309 def saveFile(self, config_file): 1343 def saveFile(self, config_file):
1310 """Save the config file (as a yaml file only right now). 1344 """Save the config file (as a yaml file only right now).
1311 """ 1345 """
1312 suffix = os.path.splitext(config_file)[1] 1346 suffix = os.path.splitext(config_file)[1]
1313 if suffix != '.yml' and suffix != '.yaml': 1347 if suffix != '.yml' and suffix != '.yaml':
1314 illegal_value(suffix, 'config file extension', 'saveFile') 1348 illegal_value(suffix, 'config file extension', 'Config.saveFile')
1315 1349
1316 # Check if config file exists 1350 # Check if config file exists
1317 if os.path.isfile(config_file): 1351 if os.path.isfile(config_file):
1318 logging.info(f'Updating {config_file}') 1352 logging.info(f'Updating {config_file}')
1319 else: 1353 else:
1322 # Save config file 1356 # Save config file
1323 with open(config_file, 'w') as f: 1357 with open(config_file, 'w') as f:
1324 yaml.safe_dump(self.config, f) 1358 yaml.safe_dump(self.config, f)
1325 1359
1326 def validate(self, pars_required, pars_missing=None): 1360 def validate(self, pars_required, pars_missing=None):
1327 """Returns False if any required first level keys are missing. 1361 """Returns False if any required keys are missing.
1328 """ 1362 """
1329 if not self.load_flag: 1363 if not self.load_flag:
1330 logging.error('Load a config file prior to calling Config.validate') 1364 logging.error('Load a config file prior to calling Config.validate')
1331 pars = [p for p in pars_required if p not in self.config] 1365
1332 if isinstance(pars_missing, list): 1366 def validate_nested_pars(config, par):
1333 pars_missing.extend(pars) 1367 par_levels = par.split(':')
1334 elif pars_missing is not None: 1368 first_level_par = par_levels[0]
1335 illegal_value(pars_missing, 'pars_missing', 'Config.validate') 1369 try:
1336 if len(pars) > 0: 1370 first_level_par = int(first_level_par)
1371 except:
1372 pass
1373 try:
1374 next_level_config = config[first_level_par]
1375 if len(par_levels) > 1:
1376 next_level_par = ':'.join(par_levels[1:])
1377 return validate_nested_pars(next_level_config, next_level_par)
1378 else:
1379 return True
1380 except:
1381 return False
1382
1383 pars_missing = [p for p in pars_required if not validate_nested_pars(self.config, p)]
1384 if len(pars_missing) > 0:
1385 logging.error(f'Missing item(s) in configuration: {", ".join(pars_missing)}')
1337 return False 1386 return False
1338 return True 1387 else:
1388 return True