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