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 if len(ds.relax_gui.analyses) == 0: 133 return 134 135 # Return the name. 136 return ds.relax_gui.analyses[self._current]
137 138
139 - def current_analysis_name(self):
140 """Return the name of the current analysis. 141 142 @return: The name of the current analysis. 143 @rtype: str 144 """ 145 146 # No current page. 147 if self._current == None: 148 return 149 if len(ds.relax_gui.analyses) == 0: 150 return 151 152 # Return the name. 153 return ds.relax_gui.analyses[self._current].analysis_name
154 155
156 - def current_analysis_type(self):
157 """Return the type of the current analysis. 158 159 @return: The type of the current analysis. 160 @rtype: str 161 """ 162 163 # No current page. 164 if self._current == None: 165 return 166 if len(ds.relax_gui.analyses) == 0: 167 return 168 169 # Return the name. 170 return ds.relax_gui.analyses[self._current].analysis_type
171 172
173 - def delete_all(self, reset=False):
174 """Remove all analyses.""" 175 176 # Debugging set up. 177 if status.debug: 178 fn_name = sys._getframe().f_code.co_name 179 mod_name = inspect.getmodule(inspect.stack()[1][0]).__name__ 180 class_name = self.__class__.__name__ 181 full_name = "%s.%s.%s" % (mod_name, class_name, fn_name) 182 print("\n\n") 183 print("debug> %s: Deleting all analyses." % full_name) 184 185 # Unregister all observer objects prior to analysis deletion. This is to prevent queued wx events being sent to dead or non-existent objects. 186 if status.debug: 187 print("debug> %s: Unregistering all methods with the observer objects." % full_name) 188 for i in range(self._num_analyses): 189 self._analyses[i].observer_register(remove=True) 190 191 # Delete the current tabs. 192 while self._num_analyses: 193 # Flush all pending events (bug fix for MS Windows). 194 #wx.Yield() 195 196 # Remove the last analysis, until there is nothing left. 197 if status.debug: 198 print("debug> %s: Deleting the analysis at index %s." % (full_name, self._num_analyses-1)) 199 self.delete_analysis(self._num_analyses-1, reset=reset) 200 201 # Notify the observers of the change. 202 if status.debug: 203 print("debug> %s: All analyses now deleted." % full_name) 204 status.observers.gui_analysis.notify()
205 206
207 - def delete_analysis(self, index, reset=False):
208 """Delete the analysis tab and data store corresponding to the index. 209 210 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. 211 212 213 @param index: The index of the analysis to delete. 214 @type index: int 215 """ 216 217 # Debugging set up. 218 if status.debug: 219 fn_name = sys._getframe().f_code.co_name 220 mod_name = inspect.getmodule(inspect.stack()[1][0]).__name__ 221 class_name = self.__class__.__name__ 222 full_name = "%s.%s.%s" % (mod_name, class_name, fn_name) 223 print("\n\n") 224 print("debug> %s: Deleting the analysis at index %s." % (full_name, index)) 225 226 # Decrement the number of analyses. 227 self._num_analyses -= 1 228 229 # Shift the current page back one if necessary. 230 if self._current > index: 231 self._current -= 1 232 if status.debug: 233 print("debug> %s: Switching the current analysis to index %s." % (full_name, self._current)) 234 235 # Execute the analysis delete method, if it exists. 236 if hasattr(self._analyses[index], 'delete'): 237 if status.debug: 238 print("debug> %s: Executing the analysis specific delete() method." % full_name) 239 self._analyses[index].delete() 240 wx.Yield() 241 242 # Delete the tab. 243 if status.debug: 244 print("debug> %s: Deleting the notebook page." % full_name) 245 self.notebook.DeletePage(index) 246 247 # Delete the tab object. 248 if status.debug: 249 print("debug> %s: Deleting the analysis GUI object." % full_name) 250 self._analyses.pop(index) 251 252 # Data store clean up. 253 if not reset: 254 # Store the pipe bundle. 255 pipe_bundle = ds.relax_gui.analyses[index].pipe_bundle 256 257 # Delete the data store object. 258 if status.debug: 259 print("debug> %s: Deleting the data store object." % full_name) 260 ds.relax_gui.analyses.pop(index) 261 262 # Delete all data pipes associated with the analysis. 263 for pipe in pipes.pipe_names(): 264 if pipes.get_bundle(pipe) == pipe_bundle: 265 if status.debug: 266 print("debug> %s: Deleting the data pipe '%s' from the '%s' bundle." % (full_name, pipe, pipe_bundle)) 267 pipes.delete(pipe) 268 269 # No more analyses, so in the initial state. 270 if self._num_analyses == 0: 271 if status.debug: 272 print("debug> %s: Setting the initial state." % full_name) 273 self.set_init_state() 274 275 # The current page has been deleted, so handle page switching to another page. 276 elif index == self._current: 277 # Default to the current page index - so that the switch is to the next page. 278 page_index = self._current 279 280 # Switch back one page. 281 if self._num_analyses <= self._current: 282 page_index = self._current - 1 283 284 # Make the switch. 285 if status.debug: 286 print("debug> %s: Switching to page %s." % (full_name, page_index)) 287 self.switch_page(page_index) 288 289 # Notify the observers of the change. 290 status.observers.gui_analysis.notify()
291 292
293 - def get_page_from_name(self, name):
294 """Return the page corresponding to the given name. 295 296 @return: The page which matches the given name, or nothing otherwise. 297 @rtype: wx.Frame object or None 298 """ 299 300 # Determine the analysis index. 301 found = False 302 for index in range(self._num_analyses): 303 # Match. 304 if name == ds.relax_gui.analyses[index].analysis_name: 305 found = True 306 break 307 308 # No analysis found, so return nothing. 309 if not found: 310 return 311 312 # Return the analysis page. 313 return self._analyses[index]
314 315
316 - def load_from_store(self):
317 """Recreate the analyses from the relax data store.""" 318 319 # No relax_gui data store structure, so do nothing. 320 if not hasattr(ds, 'relax_gui'): 321 return 322 323 # A remapping table. 324 map = { 325 'NOE': 'noe', 326 'R1': 'r1', 327 'R2': 'r2', 328 'model-free': 'mf', 329 'Relax-disp': 'relax_disp' 330 } 331 332 # Loop over each analysis. 333 for i in range(len(ds.relax_gui.analyses)): 334 # The analysis name. 335 if hasattr(ds.relax_gui.analyses[i], 'analysis_name'): 336 analysis_name = ds.relax_gui.analyses[i].analysis_name 337 elif ds.relax_gui.analyses[i].analysis_type == 'NOE': 338 analysis_name = 'Steady-state NOE' 339 elif ds.relax_gui.analyses[i].analysis_type == 'R1': 340 analysis_name = 'R1 relaxation' 341 elif ds.relax_gui.analyses[i].analysis_type == 'R2': 342 analysis_name = 'R2 relaxation' 343 elif ds.relax_gui.analyses[i].analysis_type == 'model-free': 344 analysis_name = 'Model-free' 345 elif ds.relax_gui.analyses[i].analysis_type == 'Relax-disp': 346 analysis_name = 'Relaxation dispersion' 347 348 # Compatibility with old save files. 349 if not hasattr(ds.relax_gui.analyses[i], 'pipe_bundle'): 350 # First alias the pipe name as the bundle name. 351 ds.relax_gui.analyses[i].pipe_bundle = ds.relax_gui.analyses[i].pipe_name 352 353 # Then bundle the associated pipe into a bundle with the same name. 354 self.gui.interpreter.apply('pipe.bundle', pipe=ds.relax_gui.analyses[i].pipe_name, bundle=ds.relax_gui.analyses[i].pipe_name) 355 356 # Set up the analysis. 357 self._switch_flag = False 358 self.new_analysis(map[ds.relax_gui.analyses[i].analysis_type], analysis_name, index=i) 359 360 # Switch to the page of the current data pipe. 361 self.pipe_switch() 362 363 # Reset the switching flag. 364 self._switch_flag = True 365 366 # Notify the observers of the change. 367 status.observers.gui_analysis.notify()
368 369
370 - def menu_close(self, event):
371 """Close the currently opened analysis. 372 373 @param event: The wx event. 374 @type event: wx event 375 """ 376 377 # Notebook not created yet, so skip. 378 if not hasattr(self, 'notebook'): 379 return 380 381 # Execution lock. 382 if status.exec_lock.locked(): 383 return 384 385 # Get the current analysis index. 386 index = self.notebook.GetSelection() 387 388 # Ask if this should be done. 389 msg = "Are you sure you would like to close the current %s analysis tab?" % ds.relax_gui.analyses[index].analysis_type 390 if status.show_gui and Question(msg, title="Close current analysis", size=(350, 140), default=False).ShowModal() == wx.ID_NO: 391 return 392 393 # Delete. 394 self.delete_analysis(index)
395 396
397 - def menu_close_all(self, event):
398 """Close all analyses. 399 400 @param event: The wx event. 401 @type event: wx event 402 """ 403 404 # Notebook not created yet, so skip. 405 if not hasattr(self, 'notebook'): 406 return 407 408 # Execution lock. 409 if status.exec_lock.locked(): 410 return 411 412 # Ask if this should be done. 413 msg = "Are you sure you would like to close all analyses? All data will be erased and the relax data store reset." 414 if status.show_gui and Question(msg, title="Close all analyses", size=(350, 150), default=False).ShowModal() == wx.ID_NO: 415 return 416 417 # Delete. 418 self.delete_all() 419 420 # Reset relax. 421 reset()
422 423
424 - def menu_new(self, event, destroy=True):
425 """Launch a wizard to select the new analysis. 426 427 @param event: The wx event. 428 @type event: wx event 429 @keyword destroy: A flag which if True will cause the analysis wizard to be destroyed. This is used for the test suite. 430 @type destroy: bool 431 """ 432 433 # Execution lock. 434 if status.exec_lock.locked(): 435 return 436 437 # Initialise the analysis wizard, and obtain the user specified data. 438 self.new_wizard = Analysis_wizard() 439 data = self.new_wizard.run() 440 441 # Delete the wizard GUI element. 442 if destroy: 443 wx.Yield() 444 self.new_wizard.Destroy() 445 446 # Failure, so do nothing. 447 if data == None: 448 return 449 450 # Unpack the data. 451 analysis_type, analysis_name, pipe_name, pipe_bundle, uf_exec = data 452 453 # Initialise the new analysis. 454 self.new_analysis(analysis_type, analysis_name, pipe_name, pipe_bundle, uf_exec)
455 456
457 - def new_analysis(self, analysis_type=None, analysis_name=None, pipe_name=None, pipe_bundle=None, uf_exec=[], index=None):
458 """Initialise a new analysis. 459 460 @keyword analysis_type: The type of analysis to initialise. This can be one of 'noe', 'r1', 'r2', 'mf' or 'relax_disp'. 461 @type analysis_type: str 462 @keyword analysis_name: The name of the analysis to initialise. 463 @type analysis_name: str 464 @keyword pipe_name: The name of the original data pipe to create for the analysis. 465 @type pipe_name: str 466 @keyword pipe_bundle: The name of the data pipe bundle to associate with this analysis. 467 @type pipe_bundle: str 468 @keyword uf_exec: The list of user function on_execute methods returned from the new analysis wizard. 469 @type uf_exec: list of methods 470 @keyword index: The index of the analysis in the relax data store (set to None if no data currently exists). 471 @type index: None or int 472 """ 473 474 # Check the C modules. 475 if analysis_type in ['r1', 'r2'] and not dep_check.C_module_exp_fn: 476 error_message("Relaxation curve fitting is not available. Try compiling the C modules on your platform.") 477 return 478 479 # Check the C modules. 480 if analysis_type == 'relax_disp' and not dep_check.C_module_exp_fn: 481 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.") 482 483 # Freeze the GUI. 484 wx.Yield() 485 wx.BeginBusyCursor() 486 self.gui.Freeze() 487 488 # Starting from the initial state. 489 if self.init_state: 490 # A new sizer for the notebook (to replace the current sizer). 491 sizer = wx.BoxSizer(wx.VERTICAL) 492 493 # Create a notebook and add it to the sizer. 494 self.notebook = wx.Notebook(self.gui, -1, style=wx.NB_TOP) 495 sizer.Add(self.notebook, 1, wx.ALL|wx.EXPAND, 0) 496 497 # Bind changing events. 498 self.gui.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGING, self.on_page_changing) 499 self.gui.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, self.on_page_changed) 500 501 # Delete the previous sizer. 502 old_sizer = self.gui.GetSizer() 503 old_sizer.DeleteWindows() 504 505 # Add the new sizer to the main window. 506 self.gui.SetSizer(sizer) 507 sizer.Layout() 508 509 # The analysis classes. 510 classes = { 511 'noe': Auto_noe, 512 'r1': Auto_r1, 513 'r2': Auto_r2, 514 'mf': Auto_model_free, 515 'relax_disp': Auto_relax_disp 516 } 517 518 # Bad analysis type. 519 if analysis_type not in classes: 520 raise RelaxError("The analysis '%s' is unknown." % analysis_type) 521 522 # Initialise the class. 523 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) 524 525 # Failure. 526 if not analysis.init_flag: 527 # Reset. 528 if self.init_state: 529 self.set_init_state() 530 531 # Stop operation. 532 return 533 534 # Append the class object to the analysis window object. 535 self._analyses.append(analysis) 536 537 # Add to the notebook. 538 self.notebook.AddPage(self._analyses[-1], analysis_name) 539 540 # Increment the number of analyses. 541 self._num_analyses += 1 542 543 # Switch to the new page. 544 if self._switch_flag: 545 self.switch_page(self._num_analyses-1) 546 547 # Set the initialisation flag. 548 self.init_state = False 549 550 # Reset the main window layout. 551 self.gui.Layout() 552 553 # Thaw the GUI. 554 self.gui.Thaw() 555 if wx.IsBusy(): 556 wx.EndBusyCursor() 557 558 # Notify the observers of the change. 559 status.observers.gui_analysis.notify()
560 561
562 - def on_page_changing(self, event):
563 """Block page changing if needed. 564 565 @param event: The wx event. 566 @type event: wx event 567 """ 568 569 # Execution lock. 570 if status.exec_lock.locked(): 571 # Show an error message. 572 error_message("Cannot change analyses, relax is currently executing.", "relax execution lock") 573 574 # Veto the event. 575 event.Veto()
576 577
578 - def on_page_changed(self, event):
579 """Handle page changes. 580 581 @param event: The wx event. 582 @type event: wx event 583 """ 584 585 # The index. 586 self._current = event.GetSelection() 587 588 # Handel calls to the reset user function! 589 if ds.is_empty(): 590 return 591 592 # Switch to the major data pipe of that page if not the current one. 593 if self._switch_flag and pipes.cdp_name() != ds.relax_gui.analyses[self._current].pipe_name: 594 self.gui.interpreter.apply('pipe.switch', ds.relax_gui.analyses[self._current].pipe_name) 595 596 # Normal operation. 597 event.Skip() 598 599 # Notify the observers of the change. 600 status.observers.gui_analysis.notify()
601 602
603 - def page_index_from_bundle(self, bundle):
604 """Find the analysis associated with the data pipe bundle and return its page index. 605 606 @param bundle: The data pipe bundle to find the page of. 607 @type bundle: str 608 @return: The page index. 609 @rtype: int or None 610 """ 611 612 # Find the index. 613 index = None 614 for i in range(self._num_analyses): 615 # Matching page. 616 if ds.relax_gui.analyses[i].pipe_bundle == bundle: 617 index = i 618 break 619 620 # Return the index. 621 return index
622 623
624 - def page_name_from_bundle(self, bundle):
625 """Find the analysis associated with the bundle and return its name. 626 627 @param bundle: The data pipe bundle to find the page of. 628 @type bundle: str 629 @return: The page name. 630 @rtype: str or None 631 """ 632 633 # Find the index. 634 index = self.page_index_from_bundle(bundle) 635 636 # No matching page. 637 if index == None: 638 return 639 640 # Return the page name. 641 return ds.relax_gui.analyses[index].analysis_name
642 643
644 - def pipe_deletion(self):
645 """Remove analysis tabs for which the associated data pipe has been deleted.""" 646 647 # Loop over the analyses, noting which no longer have an associated data pipe bundle. 648 del_list = [] 649 for i in range(self._num_analyses): 650 if not pipes.has_bundle(ds.relax_gui.analyses[i].pipe_bundle): 651 del_list.append(i) 652 653 # Reverse the order of the list so the removal works correctly. 654 del_list.reverse() 655 656 # Delete the analyses. 657 for index in del_list: 658 self.delete_analysis(index)
659 660
661 - def pipe_switch(self, pipe=None):
662 """Switch the page to the given or current data pipe. 663 664 @keyword pipe: The pipe associated with the page to switch to. If not supplied, the current data pipe will be used. 665 @type pipe: str or None 666 """ 667 668 # The data pipe. 669 if pipe == None: 670 pipe = pipes.cdp_name() 671 672 # No pipes to switch to. 673 if pipe == None: 674 return 675 676 # Find the page. 677 index = self.page_index_from_bundle(pipes.get_bundle(pipe)) 678 679 # No matching page. 680 if index == None: 681 return 682 683 # The page is already active, so do nothing. 684 if self._current == index: 685 return 686 687 # Switch to the page. 688 self.switch_page(index) 689 690 # Notify the observers of the change. 691 status.observers.gui_analysis.notify()
692 693
694 - def post_reset(self):
695 """Post relax data store reset event handler.""" 696 697 # Defer to the delete_all() method. 698 self.delete_all(reset=True)
699 700
701 - def set_init_state(self):
702 """Revert to the initial state.""" 703 704 # Reset the flag. 705 self.init_state = True 706 self._current = None 707 708 # Delete the previous sizer. 709 old_sizer = self.gui.GetSizer() 710 old_sizer.DeleteWindows() 711 712 # Delete the notebook. 713 if hasattr(self, 'notebook'): 714 del self.notebook 715 716 # Recreate the GUI data store object (needed if the reset user function is called). 717 if not hasattr(ds, 'relax_gui'): 718 ds.relax_gui = Gui() 719 720 # Recreate the start screen. 721 self.gui.add_start_screen()
722 723
724 - def switch_page(self, index):
725 """Switch to the given page. 726 727 @param index: The index of the page to switch to. 728 @type index: int 729 """ 730 731 # Set the current page number. 732 self._current = index 733 734 # Switch to the major data pipe of the page if not the current one. 735 if pipes.cdp_name() != ds.relax_gui.analyses[self._current].pipe_name: 736 self.gui.interpreter.apply('pipe.switch', ds.relax_gui.analyses[self._current].pipe_name) 737 738 # Switch to the page. 739 wx.CallAfter(self.notebook.SetSelection, self._current) 740 741 # Notify the observers of the change. 742 wx.CallAfter(status.observers.gui_analysis.notify)
743