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

Source Code for Package gui.analyses

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