Package gui :: Package analyses
[hide private]
[frames] | no frames]

Source Code for Package gui.analyses

  1  ############################################################################### 
  2  #                                                                             # 
  3  # Copyright (C) 2010-2014 Edward d'Auvergne                                   # 
  4  #                                                                             # 
  5  # This file is part of the program relax (http://www.nmr-relax.com).          # 
  6  #                                                                             # 
  7  # This program is free software: you can redistribute it and/or modify        # 
  8  # it under the terms of the GNU General Public License as published by        # 
  9  # the Free Software Foundation, either version 3 of the License, or           # 
 10  # (at your option) any later version.                                         # 
 11  #                                                                             # 
 12  # This program is distributed in the hope that it will be useful,             # 
 13  # but WITHOUT ANY WARRANTY; without even the implied warranty of              # 
 14  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               # 
 15  # GNU General Public License for more details.                                # 
 16  #                                                                             # 
 17  # You should have received a copy of the GNU General Public License           # 
 18  # along with this program.  If not, see <http://www.gnu.org/licenses/>.       # 
 19  #                                                                             # 
 20  ############################################################################### 
 21   
 22  # Package docstring. 
 23  """Package for the automatic and custom analysis GUI elements.""" 
 24   
 25  # Python module imports. 
 26  import inspect 
 27  import sys 
 28  import wx 
 29  from types import ListType 
 30   
 31  # relax module imports. 
 32  from data_store import Relax_data_store; ds = Relax_data_store() 
 33  from data_store.gui import Gui 
 34  import dep_check 
 35  from gui.analyses.auto_model_free import Auto_model_free 
 36  from gui.analyses.auto_noe import Auto_noe 
 37  from gui.analyses.auto_r1 import Auto_r1 
 38  from gui.analyses.auto_r2 import Auto_r2 
 39  from gui.analyses.auto_relax_disp import Auto_relax_disp 
 40  from gui.analyses.wizard import Analysis_wizard 
 41  from gui.message import error_message, Question 
 42  from lib.errors import RelaxError 
 43  from pipe_control import pipes 
 44  from pipe_control.reset import reset 
 45  from status import Status; status = Status() 
 46   
 47   
 48  # The package contents. 
 49  __all__ = ['auto_model_free', 
 50             'auto_noe', 
 51             'auto_r1', 
 52             'auto_r2', 
 53             'auto_relax_disp', 
 54             'auto_rx_base', 
 55             'base', 
 56             'elements', 
 57             'relax_control', 
 58             'results_analysis', 
 59             'results'] 
 60   
 61   
62 -class Analysis_controller:
63 """Class for controlling all aspects of analyses.""" 64
65 - def __init__(self, gui):
66 """Initialise the analysis controller. 67 68 @param gui: The gui object. 69 @type gui: wx object 70 """ 71 72 # Store the args. 73 self.gui = gui 74 75 # Initialise some variables. 76 self.init_state = True 77 self._current = None 78 self._num_analyses = 0 79 self._switch_flag = True 80 81 # The analyses page objects. 82 self._analyses = [] 83 84 # Register the page switch method for pipe switches. 85 self.name = 'notebook page switcher' 86 status.observers.pipe_alteration.register(self.name, self.pipe_switch, method_name='pipe_switch') 87 88 # Register a method for removing analyses if the associated pipe is deleted. 89 status.observers.pipe_alteration.register('notebook pipe deletion', self.pipe_deletion, method_name='pipe_deletion') 90 91 # Register the deletion of all analyses for the reset status observer. 92 status.observers.reset.register('gui analyses', self.post_reset, method_name='post_reset') 93 94 # Register state loading. 95 status.observers.state_load.register('gui analyses', self.load_from_store, method_name='load_from_store')
96 97
98 - def analysis_data_loop(self):
99 """Loop over the analyses, yielding the data objects. 100 101 @return: The analysis data object from the relax data store. 102 @rtype: data.gui.Analyses instance 103 """ 104 105 # Loop over the analyses. 106 for i in range(self._num_analyses): 107 yield ds.relax_gui.analyses[i]
108 109
110 - def analysis_loop(self):
111 """Loop over the analyses, yielding the page objects. 112 113 @return: The page object. 114 @rtype: wx.Frame object 115 """ 116 117 # Loop over the analyses. 118 for i in range(self._num_analyses): 119 yield self._analyses[i]
120 121
122 - def current_data(self):
123 """Return the data container of the current analysis from the relax data store. 124 125 @return: The data container of the current analysis. 126 @rtype: str 127 """ 128 129 # No current page. 130 if self._current == None: 131 return 132 133 # Return the name. 134 return ds.relax_gui.analyses[self._current]
135 136
137 - def current_analysis_name(self):
138 """Return the name of the current analysis. 139 140 @return: The name of the current analysis. 141 @rtype: str 142 """ 143 144 # No current page. 145 if self._current == None: 146 return 147 148 # Return the name. 149 return ds.relax_gui.analyses[self._current].analysis_name
150 151
152 - def current_analysis_type(self):
153 """Return the type of the current analysis. 154 155 @return: The type of the current analysis. 156 @rtype: str 157 """ 158 159 # No current page. 160 if self._current == None: 161 return 162 163 # Return the name. 164 return ds.relax_gui.analyses[self._current].analysis_type
165 166
167 - def delete_all(self):
168 """Remove all analyses.""" 169 170 # Debugging set up. 171 if status.debug: 172 fn_name = sys._getframe().f_code.co_name 173 mod_name = inspect.getmodule(inspect.stack()[1][0]).__name__ 174 class_name = self.__class__.__name__ 175 full_name = "%s.%s.%s" % (mod_name, class_name, fn_name) 176 print("\n\n") 177 print("debug> %s: Deleting all analyses." % full_name) 178 179 # Unregister all observer objects prior to analysis deletion. This is to prevent queued wx events being sent to dead or non-existent objects. 180 if status.debug: 181 print("debug> %s: Unregistering all methods with the observer objects." % full_name) 182 for i in range(self._num_analyses): 183 self._analyses[i].observer_register(remove=True) 184 185 # Delete the current tabs. 186 while self._num_analyses: 187 # Flush all pending events (bug fix for MS Windows). 188 #wx.Yield() 189 190 # Remove the last analysis, until there is nothing left. 191 if status.debug: 192 print("debug> %s: Deleting the analysis at index %s." % (full_name, self._num_analyses-1)) 193 self.delete_analysis(self._num_analyses-1) 194 195 # Notify the observers of the change. 196 if status.debug: 197 print("debug> %s: All analyses now deleted." % full_name) 198 status.observers.gui_analysis.notify()
199 200
201 - def delete_analysis(self, index):
202 """Delete the analysis tab and data store corresponding to the index. 203 204 The order of these operations is very important due to the notification of observer objects and the updates, synchronisations, etc. that follow. If the program debugging mode is on, then printouts at each stage will occur to allow the following of the code and observer object notifications. 205 206 207 @param index: The index of the analysis to delete. 208 @type index: int 209 """ 210 211 # Debugging set up. 212 if status.debug: 213 fn_name = sys._getframe().f_code.co_name 214 mod_name = inspect.getmodule(inspect.stack()[1][0]).__name__ 215 class_name = self.__class__.__name__ 216 full_name = "%s.%s.%s" % (mod_name, class_name, fn_name) 217 print("\n\n") 218 print("debug> %s: Deleting the analysis at index %s." % (full_name, index)) 219 220 # Decrement the number of analyses. 221 self._num_analyses -= 1 222 223 # Shift the current page back one if necessary. 224 if self._current > index: 225 self._current -= 1 226 if status.debug: 227 print("debug> %s: Switching the current analysis to index %s." % (full_name, self._current)) 228 229 # Execute the analysis delete method, if it exists. 230 if hasattr(self._analyses[index], 'delete'): 231 if status.debug: 232 print("debug> %s: Executing the analysis specific delete() method." % full_name) 233 self._analyses[index].delete() 234 235 # Delete the tab. 236 if status.debug: 237 print("debug> %s: Deleting the notebook page." % full_name) 238 self.notebook.DeletePage(index) 239 240 # Delete the tab object. 241 if status.debug: 242 print("debug> %s: Deleting the analysis GUI object." % full_name) 243 self._analyses.pop(index) 244 245 # Store the pipe bundle. 246 pipe_bundle = ds.relax_gui.analyses[index].pipe_bundle 247 248 # Delete the data store object. 249 if status.debug: 250 print("debug> %s: Deleting the data store object." % full_name) 251 ds.relax_gui.analyses.pop(index) 252 253 # Delete all data pipes associated with the analysis. 254 for pipe in pipes.pipe_names(): 255 if pipes.get_bundle(pipe) == pipe_bundle: 256 if status.debug: 257 print("debug> %s: Deleting the data pipe '%s' from the '%s' bundle." % (full_name, pipe, pipe_bundle)) 258 pipes.delete(pipe) 259 260 # No more analyses, so in the initial state. 261 if self._num_analyses == 0: 262 if status.debug: 263 print("debug> %s: Setting the initial state." % full_name) 264 self.set_init_state() 265 266 # The current page has been deleted, so handle page switching to another page. 267 elif index == self._current: 268 # Default to the current page index - so that the switch is to the next page. 269 page_index = self._current 270 271 # Switch back one page. 272 if self._num_analyses <= self._current: 273 page_index = self._current - 1 274 275 # Make the switch. 276 if status.debug: 277 print("debug> %s: Switching to page %s." % (full_name, page_index)) 278 self.switch_page(page_index) 279 280 # Notify the observers of the change. 281 status.observers.gui_analysis.notify()
282 283
284 - def get_page_from_name(self, name):
285 """Return the page corresponding to the given name. 286 287 @return: The page which matches the given name, or nothing otherwise. 288 @rtype: wx.Frame object or None 289 """ 290 291 # Determine the analysis index. 292 found = False 293 for index in range(self._num_analyses): 294 # Match. 295 if name == ds.relax_gui.analyses[index].analysis_name: 296 found = True 297 break 298 299 # No analysis found, so return nothing. 300 if not found: 301 return 302 303 # Return the analysis page. 304 return self._analyses[index]
305 306
307 - def load_from_store(self):
308 """Recreate the analyses from the relax data store.""" 309 310 # No relax_gui data store structure, so do nothing. 311 if not hasattr(ds, 'relax_gui'): 312 return 313 314 # A remapping table. 315 map = { 316 'NOE': 'noe', 317 'R1': 'r1', 318 'R2': 'r2', 319 'model-free': 'mf', 320 'Relax-disp': 'relax_disp' 321 } 322 323 # Loop over each analysis. 324 for i in range(len(ds.relax_gui.analyses)): 325 # The analysis name. 326 if hasattr(ds.relax_gui.analyses[i], 'analysis_name'): 327 analysis_name = ds.relax_gui.analyses[i].analysis_name 328 elif ds.relax_gui.analyses[i].analysis_type == 'NOE': 329 analysis_name = 'Steady-state NOE' 330 elif ds.relax_gui.analyses[i].analysis_type == 'R1': 331 analysis_name = 'R1 relaxation' 332 elif ds.relax_gui.analyses[i].analysis_type == 'R2': 333 analysis_name = 'R2 relaxation' 334 elif ds.relax_gui.analyses[i].analysis_type == 'model-free': 335 analysis_name = 'Model-free' 336 elif ds.relax_gui.analyses[i].analysis_type == 'Relax-disp': 337 analysis_name = 'Relaxation dispersion' 338 339 # Compatibility with old save files. 340 if not hasattr(ds.relax_gui.analyses[i], 'pipe_bundle'): 341 # First alias the pipe name as the bundle name. 342 ds.relax_gui.analyses[i].pipe_bundle = ds.relax_gui.analyses[i].pipe_name 343 344 # Then bundle the associated pipe into a bundle with the same name. 345 self.gui.interpreter.apply('pipe.bundle', pipe=ds.relax_gui.analyses[i].pipe_name, bundle=ds.relax_gui.analyses[i].pipe_name) 346 347 # Set up the analysis. 348 self._switch_flag = False 349 self.new_analysis(map[ds.relax_gui.analyses[i].analysis_type], analysis_name, index=i) 350 351 # Switch to the page of the current data pipe. 352 self.pipe_switch() 353 354 # Reset the switching flag. 355 self._switch_flag = True 356 357 # Notify the observers of the change. 358 status.observers.gui_analysis.notify()
359 360
361 - def menu_close(self, event):
362 """Close the currently opened analysis. 363 364 @param event: The wx event. 365 @type event: wx event 366 """ 367 368 # Notebook not created yet, so skip. 369 if not hasattr(self, 'notebook'): 370 return 371 372 # Execution lock. 373 if status.exec_lock.locked(): 374 return 375 376 # Get the current analysis index. 377 index = self.notebook.GetSelection() 378 379 # Ask if this should be done. 380 msg = "Are you sure you would like to close the current %s analysis tab?" % ds.relax_gui.analyses[index].analysis_type 381 if status.show_gui and Question(msg, title="Close current analysis", size=(350, 140), default=False).ShowModal() == wx.ID_NO: 382 return 383 384 # Delete. 385 self.delete_analysis(index)
386 387
388 - def menu_close_all(self, event):
389 """Close all analyses. 390 391 @param event: The wx event. 392 @type event: wx event 393 """ 394 395 # Notebook not created yet, so skip. 396 if not hasattr(self, 'notebook'): 397 return 398 399 # Execution lock. 400 if status.exec_lock.locked(): 401 return 402 403 # Ask if this should be done. 404 msg = "Are you sure you would like to close all analyses? All data will be erased and the relax data store reset." 405 if status.show_gui and Question(msg, title="Close all analyses", size=(350, 150), default=False).ShowModal() == wx.ID_NO: 406 return 407 408 # Delete. 409 self.delete_all() 410 411 # Reset relax. 412 reset()
413 414
415 - def menu_new(self, event):
416 """Launch a wizard to select the new analysis. 417 418 @param event: The wx event. 419 @type event: wx event 420 """ 421 422 # Execution lock. 423 if status.exec_lock.locked(): 424 return 425 426 # Initialise the analysis wizard, and obtain the user specified data. 427 self.new_wizard = Analysis_wizard() 428 data = self.new_wizard.run() 429 430 # Failure, so do nothing. 431 if data == None: 432 return 433 434 # Unpack the data. 435 analysis_type, analysis_name, pipe_name, pipe_bundle, uf_exec = data 436 437 # Initialise the new analysis. 438 self.new_analysis(analysis_type, analysis_name, pipe_name, pipe_bundle, uf_exec) 439 440 # Delete the wizard data. 441 del self.new_wizard
442 443
444 - def new_analysis(self, analysis_type=None, analysis_name=None, pipe_name=None, pipe_bundle=None, uf_exec=[], index=None):
445 """Initialise a new analysis. 446 447 @keyword analysis_type: The type of analysis to initialise. This can be one of 'noe', 'r1', 'r2', 'mf' or 'relax_disp'. 448 @type analysis_type: str 449 @keyword analysis_name: The name of the analysis to initialise. 450 @type analysis_name: str 451 @keyword pipe_name: The name of the original data pipe to create for the analysis. 452 @type pipe_name: str 453 @keyword pipe_bundle: The name of the data pipe bundle to associate with this analysis. 454 @type pipe_bundle: str 455 @keyword uf_exec: The list of user function on_execute methods returned from the new analysis wizard. 456 @type uf_exec: list of methods 457 @keyword index: The index of the analysis in the relax data store (set to None if no data currently exists). 458 @type index: None or int 459 """ 460 461 # Check the C modules. 462 if analysis_type in ['r1', 'r2'] and not dep_check.C_module_exp_fn: 463 error_message("Relaxation curve fitting is not available. Try compiling the C modules on your platform.") 464 return 465 466 # Check the C modules. 467 if analysis_type == 'relax_disp' and not dep_check.C_module_exp_fn: 468 error_message("Relaxation curve fitting will not available for this dispersion analysis. Try compiling the C modules on your platform if you have measured full exponential curves.") 469 470 # Freeze the GUI. 471 wx.Yield() 472 wx.BeginBusyCursor() 473 self.gui.Freeze() 474 475 # Starting from the initial state. 476 if self.init_state: 477 # A new sizer for the notebook (to replace the current sizer). 478 sizer = wx.BoxSizer(wx.VERTICAL) 479 480 # Create a notebook and add it to the sizer. 481 self.notebook = wx.Notebook(self.gui, -1, style=wx.NB_TOP) 482 sizer.Add(self.notebook, 1, wx.ALL|wx.EXPAND, 0) 483 484 # Bind changing events. 485 self.gui.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGING, self.on_page_changing) 486 self.gui.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, self.on_page_changed) 487 488 # Delete the previous sizer. 489 old_sizer = self.gui.GetSizer() 490 old_sizer.DeleteWindows() 491 492 # Add the new sizer to the main window. 493 self.gui.SetSizer(sizer) 494 sizer.Layout() 495 496 # The analysis classes. 497 classes = { 498 'noe': Auto_noe, 499 'r1': Auto_r1, 500 'r2': Auto_r2, 501 'mf': Auto_model_free, 502 'relax_disp': Auto_relax_disp 503 } 504 505 # Bad analysis type. 506 if analysis_type not in classes.keys(): 507 raise RelaxError("The analysis '%s' is unknown." % analysis_type) 508 509 # Initialise the class. 510 analysis = classes[analysis_type](parent=self.notebook, id=-1, gui=self.gui, analysis_name=analysis_name, pipe_name=pipe_name, pipe_bundle=pipe_bundle, uf_exec=uf_exec, data_index=index) 511 512 # Failure. 513 if not analysis.init_flag: 514 # Reset. 515 if self.init_state: 516 self.set_init_state() 517 518 # Stop operation. 519 return 520 521 # Append the class object to the analysis window object. 522 self._analyses.append(analysis) 523 524 # Add to the notebook. 525 self.notebook.AddPage(self._analyses[-1], analysis_name) 526 527 # Increment the number of analyses. 528 self._num_analyses += 1 529 530 # Switch to the new page. 531 if self._switch_flag: 532 self.switch_page(self._num_analyses-1) 533 534 # Set the initialisation flag. 535 self.init_state = False 536 537 # Reset the main window layout. 538 self.gui.Layout() 539 540 # Thaw the GUI. 541 self.gui.Thaw() 542 if wx.IsBusy(): 543 wx.EndBusyCursor() 544 545 # Notify the observers of the change. 546 status.observers.gui_analysis.notify()
547 548
549 - def on_page_changing(self, event):
550 """Block page changing if needed. 551 552 @param event: The wx event. 553 @type event: wx event 554 """ 555 556 # Execution lock. 557 if status.exec_lock.locked(): 558 # Show an error message. 559 error_message("Cannot change analyses, relax is currently executing.", "relax execution lock") 560 561 # Veto the event. 562 event.Veto()
563 564
565 - def on_page_changed(self, event):
566 """Handle page changes. 567 568 @param event: The wx event. 569 @type event: wx event 570 """ 571 572 # The index. 573 self._current = event.GetSelection() 574 575 # Handel calls to the reset user function! 576 if ds.is_empty(): 577 return 578 579 # Switch to the major data pipe of that page if not the current one. 580 if self._switch_flag and pipes.cdp_name() != ds.relax_gui.analyses[self._current].pipe_name: 581 self.gui.interpreter.apply('pipe.switch', ds.relax_gui.analyses[self._current].pipe_name) 582 583 # Normal operation. 584 event.Skip() 585 586 # Notify the observers of the change. 587 status.observers.gui_analysis.notify()
588 589
590 - def page_index_from_bundle(self, bundle):
591 """Find the analysis associated with the data pipe bundle and return its page index. 592 593 @param bundle: The data pipe bundle to find the page of. 594 @type bundle: str 595 @return: The page index. 596 @rtype: int or None 597 """ 598 599 # Find the index. 600 index = None 601 for i in range(self._num_analyses): 602 # Matching page. 603 if ds.relax_gui.analyses[i].pipe_bundle == bundle: 604 index = i 605 break 606 607 # Return the index. 608 return index
609 610
611 - def page_name_from_bundle(self, bundle):
612 """Find the analysis associated with the bundle and return its name. 613 614 @param bundle: The data pipe bundle to find the page of. 615 @type bundle: str 616 @return: The page name. 617 @rtype: str or None 618 """ 619 620 # Find the index. 621 index = self.page_index_from_bundle(bundle) 622 623 # No matching page. 624 if index == None: 625 return 626 627 # Return the page name. 628 return ds.relax_gui.analyses[index].analysis_name
629 630
631 - def pipe_deletion(self):
632 """Remove analysis tabs for which the associated data pipe has been deleted.""" 633 634 # Loop over the analyses, noting which no longer have an associated data pipe bundle. 635 del_list = [] 636 for i in range(self._num_analyses): 637 if not pipes.has_bundle(ds.relax_gui.analyses[i].pipe_bundle): 638 del_list.append(i) 639 640 # Reverse the order of the list so the removal works correctly. 641 del_list.reverse() 642 643 # Delete the analyses. 644 for index in del_list: 645 self.delete_analysis(index)
646 647
648 - def pipe_switch(self, pipe=None):
649 """Switch the page to the given or current data pipe. 650 651 @keyword pipe: The pipe associated with the page to switch to. If not supplied, the current data pipe will be used. 652 @type pipe: str or None 653 """ 654 655 # The data pipe. 656 if pipe == None: 657 pipe = pipes.cdp_name() 658 659 # No pipes to switch to. 660 if pipe == None: 661 return 662 663 # Find the page. 664 index = self.page_index_from_bundle(pipes.get_bundle(pipe)) 665 666 # No matching page. 667 if index == None: 668 return 669 670 # The page is already active, so do nothing. 671 if self._current == index: 672 return 673 674 # Switch to the page. 675 self.switch_page(index) 676 677 # Notify the observers of the change. 678 status.observers.gui_analysis.notify()
679 680
681 - def post_reset(self):
682 """Post relax data store reset event handler.""" 683 684 # Debugging set up. 685 if status.debug: 686 fn_name = sys._getframe().f_code.co_name 687 mod_name = inspect.getmodule(inspect.stack()[1][0]).__name__ 688 class_name = self.__class__.__name__ 689 full_name = "%s.%s.%s" % (mod_name, class_name, fn_name) 690 print("\n\n") 691 print("debug> %s: Deleting all analyses." % full_name) 692 693 # Unregister all observer objects prior to analysis deletion. This is to prevent queued wx events being sent to dead or non-existent objects. 694 if status.debug: 695 print("debug> %s: Unregistering all methods with the observer objects." % full_name) 696 for i in range(self._num_analyses): 697 self._analyses[i].observer_register(remove=True) 698 699 # Delete all tabs. 700 while self._num_analyses: 701 # The index of the tab to remove. 702 index = self._num_analyses - 1 703 704 # Delete the tab. 705 if hasattr(self, 'notebook'): 706 self.notebook.DeletePage(index) 707 708 # Delete the tab object. 709 self._analyses.pop(index) 710 711 # Decrement the number of analyses. 712 self._num_analyses -= 1 713 714 # Set the initial state. 715 self.set_init_state()
716 717
718 - def set_init_state(self):
719 """Revert to the initial state.""" 720 721 # Reset the flag. 722 self.init_state = True 723 self._current = None 724 725 # Delete the previous sizer. 726 old_sizer = self.gui.GetSizer() 727 old_sizer.DeleteWindows() 728 729 # Delete the notebook. 730 if hasattr(self, 'notebook'): 731 del self.notebook 732 733 # Recreate the GUI data store object (needed if the reset user function is called). 734 if not hasattr(ds, 'relax_gui'): 735 ds.relax_gui = Gui() 736 737 # Recreate the start screen. 738 self.gui.add_start_screen()
739 740
741 - def switch_page(self, index):
742 """Switch to the given page. 743 744 @param index: The index of the page to switch to. 745 @type index: int 746 """ 747 748 # Set the current page number. 749 self._current = index 750 751 # Switch to the major data pipe of the page if not the current one. 752 if pipes.cdp_name() != ds.relax_gui.analyses[self._current].pipe_name: 753 self.gui.interpreter.apply('pipe.switch', ds.relax_gui.analyses[self._current].pipe_name) 754 755 # Switch to the page. 756 wx.CallAfter(self.notebook.SetSelection, self._current) 757 758 # Notify the observers of the change. 759 wx.CallAfter(status.observers.gui_analysis.notify)
760