Changeset 5032ea68 in sasview


Ignore:
Timestamp:
Jun 14, 2016 4:51:18 AM (8 years ago)
Author:
Piotr Rozyczko <piotr.rozyczko@…>
Branches:
ESS_GUI, ESS_GUI_Docs, ESS_GUI_batch_fitting, ESS_GUI_bumps_abstraction, ESS_GUI_iss1116, ESS_GUI_iss879, ESS_GUI_iss959, ESS_GUI_opencl, ESS_GUI_ordering, ESS_GUI_sync_sascalc
Children:
a281ab8
Parents:
488c49d
Message:

threaded file load, data object related fixes, more unit tests.

Location:
src/sas/qtgui
Files:
8 edited

Legend:

Unmodified
Added
Removed
  • src/sas/qtgui/DataExplorer.py

    r488c49d r5032ea68  
    5252        self.treeView.setModel(self.proxy) 
    5353 
    54     def loadFile(self, event): 
     54 
     55    def loadFile(self, event=None): 
    5556        """ 
    5657        Called when the "Load" button pressed. 
     
    6465        self.communicate.fileReadSignal.emit(path_str) 
    6566 
    66         # Read in the data from chosen file(s) 
    67         self.readData(path_str) 
     67        # threaded file load 
     68        load_thread = threads.deferToThread(self.readData, path_str) 
     69        load_thread.addCallback(self.loadComplete) 
    6870 
    6971        return 
    7072 
     73    def loadFolder(self, event=None): 
     74        """ 
     75        Called when the "File/Load Folder" menu item chosen. 
     76        Opens the Qt "Open Folder..." dialog  
     77        """ 
     78        dir = QtGui.QFileDialog.getExistingDirectory(self, "Choose a directory", "", 
     79              QtGui.QFileDialog.ShowDirsOnly) 
     80        if dir is None: 
     81            return 
     82 
     83        dir = str(dir) 
     84 
     85        if not os.path.isdir(dir): 
     86            return 
     87 
     88        # get content of dir into a list 
     89        path_str = [os.path.join(os.path.abspath(dir), filename) for filename in os.listdir(dir)] 
     90 
     91        # threaded file load 
     92        load_thread = threads.deferToThread(self.readData, path_str) 
     93        load_thread.addCallback(self.loadComplete) 
     94         
     95        return 
     96 
    7197    def deleteFile(self, event): 
    7298        """ 
    73         """ 
     99        Delete selected rows from the model 
     100        """ 
     101        # Assure this is indeed wanted 
     102        delete_msg = "This operation will delete the checked data sets and all the dependents." +\ 
     103                     "\nDo you want to continue?" 
     104        reply = QtGui.QMessageBox.question(self, 'Warning', delete_msg, 
     105                QtGui.QMessageBox.Yes, QtGui.QMessageBox.No) 
     106 
     107        if reply == QtGui.QMessageBox.No: 
     108            return 
     109 
    74110        # Figure out which rows are checked 
    75  
    76         # Delete these rows from the model 
    77  
    78         # Call data_manager update with delete_data() 
    79  
     111        ind = -1 
     112        # Use 'while' so the row count is forced at every iteration 
     113        while ind < self.model.rowCount(): 
     114            ind += 1 
     115            item = self.model.item(ind) 
     116            if item and item.isCheckable() and item.checkState() == QtCore.Qt.Checked: 
     117                # Delete these rows from the model 
     118                self.model.removeRow(ind) 
     119                # Decrement index since we just deleted it 
     120                ind -= 1 
     121 
     122        # pass temporarily kept as a breakpoint anchor 
    80123        pass 
    81124 
    82125    def sendData(self, event): 
    83126        """ 
    84         """ 
     127        Send selected item data to the current perspective and set the relevant notifiers 
     128        """ 
     129        # should this reside on GuiManager or here? 
     130        self._perspective = self.parent.perspective() 
     131 
     132        # Set the signal handlers 
     133        self.communicator = self._perspective.communicator() 
     134        self.communicator.updateModelFromPerspectiveSignal.connect(self.updateModelFromPerspective) 
     135 
    85136        # Figure out which rows are checked 
    86  
     137        selected_items = [] 
     138        for index in range(self.model.rowCount()): 
     139            item = self.model.item(index) 
     140            if item.isCheckable() and item.checkState() == QtCore.Qt.Checked: 
     141                selected_items.append(item) 
     142 
     143        # Which perspective has been selected? 
     144        if len(selected_items) > 1 and not self._perspective.allowBatch(): 
     145            msg = self._perspective.title() + " does not allow multiple data." 
     146            msgbox = QtGui.QMessageBox() 
     147            msgbox.setIcon(QtGui.QMessageBox.Critical) 
     148            msgbox.setText(msg) 
     149            msgbox.setStandardButtons(QtGui.QMessageBox.Ok) 
     150            retval = msgbox.exec_() 
     151            return 
    87152        # Dig up data from model 
    88         # To get the original Data1D object back use: 
    89         # object_item.data().toPyObject() 
    90  
    91  
    92         # Which perspective has been selected? 
    93  
     153        data = [selected_items[0].child(0).data().toPyObject()] 
     154 
     155        # TODO 
    94156        # New plot or appended? 
    95157 
    96158        # Notify the GuiManager about the send request 
    97         # updatePerspectiveWithDataSignal() 
    98         pass 
     159        self._perspective.setData(data_list=data) 
     160 
    99161 
    100162    def chooseFiles(self): 
    101163        """ 
     164        Shows the Open file dialog and returns the chosen path(s) 
    102165        """ 
    103166        # List of known extensions 
     
    105168 
    106169        # Location is automatically saved - no need to keep track of the last dir 
    107         # TODO: is it really? 
    108         paths = QtGui.QFileDialog.getOpenFileName(self, "Choose a file", "", wlist) 
     170        # But only with Qt built-in dialog (non-platform native) 
     171        paths = QtGui.QFileDialog.getOpenFileName(self, "Choose a file", "", 
     172                wlist, None, QtGui.QFileDialog.DontUseNativeDialog) 
    109173        if paths is None: 
    110174            return 
     
    132196        data_error = False 
    133197        error_message = "" 
     198         
    134199        for p_file in path: 
    135200            info = "info" 
     
    147212 
    148213            try: 
    149                 message = "Loading Data... " + str(p_file) + "\n" 
     214                message = "Loading Data... " + str(basename) + "\n" 
    150215 
    151216                # change this to signal notification in GuiManager 
    152217                self.communicate.statusBarUpdateSignal.emit(message) 
    153  
    154                 # threaded file load 
    155                 # load_thread = threads.deferToThread(self.loadThread, p_file) 
    156                 # Add deferred callback for call return 
    157                 # load_thread.addCallback(self.plotResult) 
    158218 
    159219                output_objects = self.loader.load(p_file) 
     
    170230                    output[new_data.id] = new_data 
    171231                    self.updateModel(new_data, p_file) 
     232                    self.model.reset() 
     233 
     234                    QtGui.qApp.processEvents() 
    172235 
    173236                    if hasattr(item, 'errors'): 
     
    176239                            message += "\tError: {0}\n".format(error_data) 
    177240                    else: 
     241 
    178242                        logging.error("Loader returned an invalid object:\n %s" % str(item)) 
    179243                        data_error = True 
    180244 
    181             except: 
     245            except Exception as ex: 
    182246                logging.error(sys.exc_value) 
     247 
    183248                any_error = True 
    184249            if any_error or error_message != "": 
     
    207272            message = "Loading Data Complete! " 
    208273        message += log_msg 
    209         self.loadComplete(output=output, message=message) 
     274        return (output, message) 
    210275 
    211276    def getWlist(self): 
     
    314379        Post message to status bar and update the data manager 
    315380        """ 
     381        self.model.reset() 
    316382        # Notify the manager of the new data available 
    317383        self.communicate.statusBarUpdateSignal.emit(message) 
    318384        self.communicate.fileDataReceivedSignal.emit(output) 
    319  
    320385        self.manager.add_data(data_list=output) 
    321386 
     
    362427        self.proxy.setFilterRegExp(r"[^()]") 
    363428 
     429    def updateModelFromPerspective(self, model_item): 
     430        """ 
     431        """ 
     432        # Overwrite the index with what we got from the perspective 
     433        if type(model_item) != QtGui.QStandardItem: 
     434            msg = "Wrong data type returned from calculations." 
     435            raise AttributeError, msg 
     436        # self.model.insertRow(model_item) 
     437        # Reset the view 
     438        self.model.reset() 
     439        # Pass acting as a debugger anchor 
     440        pass 
    364441 
    365442    def addExtraRows(self, info_item, data): 
    366443        """ 
     444        Extract relevant data to include in the Info ModelItem 
    367445        """ 
    368446        title_item   = QtGui.QStandardItem("Title: "      + data.title) 
  • src/sas/qtgui/GuiManager.py

    rf721030 r5032ea68  
    6565        self._current_perspective = self.invariantWidget 
    6666      
    67  
    6867    def fileRead(self, data): 
    6968        """ 
    7069        """ 
    71         #print("FILE %s "%data) 
     70        print("FILE %s "%data) 
    7271        pass 
    7372     
     
    225224    def actionLoadData(self): 
    226225        """ 
    227         """ 
    228         print("actionLoadData TRIGGERED") 
    229         pass 
     226        Load file from Data Explorer 
     227        """ 
     228        self.filesWidget.loadFile() 
    230229 
    231230    def actionLoad_Data_Folder(self): 
    232231        """ 
    233232        """ 
    234         print("actionLoad_Data_Folder TRIGGERED") 
    235         pass 
     233        self.filesWidget.loadFolder() 
    236234 
    237235    def actionOpen_Project(self): 
  • src/sas/qtgui/GuiUtils.py

    rf721030 r5032ea68  
    2525#from sas.sasgui.guiframe.events import StatusEvent 
    2626#from sas.sasgui.guiframe.events import NewPlotEvent 
     27from sas.sasgui.guiframe.dataFitting import Data1D 
    2728 
    2829 
     
    202203    # Send data to the current perspective 
    203204    updatePerspectiveWithDataSignal = QtCore.pyqtSignal(list) 
     205 
     206    # New data in current perspective 
     207    updateModelFromPerspectiveSignal = QtCore.pyqtSignal(Data1D) 
  • src/sas/qtgui/Perspectives/Invariant/InvariantPerspective.py

    rf721030 r5032ea68  
    1111from sas.sascalc.invariant import invariant 
    1212from sas.sasgui.guiframe.dataFitting import Data1D 
     13from sas.qtgui.GuiUtils import Communicate 
    1314 
    1415# local 
     
    4041    def __init__(self, manager=None, parent=None): 
    4142        super(InvariantWindow, self).__init__(parent) 
    42         # This controller contains the ui and doesn't inherit it directly! 
    43         # self.form = InvariantUI() 
    4443        self.setWindowTitle("Invariant Perspective") 
    4544        # initial input params 
     
    6564        self._high_power_value  = False 
    6665 
     66        self.communicate = Communicate() 
    6767 
    6868        # Mask file selector 
     
    9292        # Set up the mapper 
    9393        self.setupMapper() 
     94 
     95    def communicator(self): 
     96        """ 
     97        """ 
     98        return self.communicate 
    9499 
    95100    def updateFromModel(self): 
     
    142147            self.pushButton.setStyleSheet(self.style) 
    143148 
     149 
    144150    def plotResult(self, model): 
    145151        """ 
     
    154160        self.pushButton.setStyleSheet(self.style) 
    155161 
     162        # Send the new data to DE for keeping in the model 
     163        self.communicate.updateModelFromPerspectiveSignal.emit(self._data) 
     164 
    156165 
    157166    def calculateThread(self, extrapolation): 
    158167        """ 
     168        Perform Invariant calculations. 
     169 
     170        TODO: Create a dictionary of results to be sent to DE on completion. 
    159171        """ 
    160172        self.updateFromModel() 
     
    294306        return self.model 
    295307                 
     308    def title(self): 
     309        """ 
     310        Perspective name 
     311        """ 
     312        return "Invariant panel" 
    296313 
    297314    def status(self): 
     
    537554            #wx.PostEvent(self.parent, StatusEvent(status=msg, info='error')) 
    538555 
    539  
     556    def allowBatch(self): 
     557        """ 
     558        Tell the caller that we don't accept multiple data instances 
     559        """ 
     560        return False 
    540561 
    541562if __name__ == "__main__": 
  • src/sas/qtgui/UnitTesting/DataExplorerTest.py

    r488c49d r5032ea68  
    11import sys 
    22import unittest 
     3#from twisted.trial import unittest 
     4#from twisted.internet import reactor, defer, interfaces, threads, protocol, error 
    35 
    46from PyQt4.QtGui import * 
     
    2022    def setUp(self): 
    2123        '''Create the GUI''' 
     24        class MyPerspective(object): 
     25            def communicator(self): 
     26                return Communicate() 
     27            def allowBatch(self): 
     28                return False 
     29            def setData(self, data_list=None): 
     30                return None 
     31            def title(self): 
     32                return "Dummy Perspective" 
     33 
    2234        class dummy_manager(object): 
    2335            def communicator(self): 
    2436                return Communicate() 
     37            def perspective(self): 
     38                return MyPerspective() 
    2539 
    2640        self.form = DataExplorerWindow(None, dummy_manager()) 
     
    6781 
    6882    def testDeleteButton(self): 
     83        """ 
     84        Functionality of the delete button 
     85        """ 
    6986 
    7087        deleteButton = self.form.cmdDelete 
    7188 
    7289        # Mock the confirmation dialog with return=Yes 
     90        QtGui.QMessageBox.question = MagicMock(return_value=QtGui.QMessageBox.No) 
    7391 
    7492        # Populate the model 
    75  
    76         # Assure the checkbox is on 
     93        filename = ["cyl_400_20.txt", "Dec07031.ASC", "cyl_400_20.txt"] 
     94        self.form.readData(filename) 
     95 
     96        # Assure the model contains three items 
     97        self.assertEqual(self.form.model.rowCount(), 3) 
     98 
     99        # Assure the checkboxes are on 
     100        item1 = self.form.model.item(0) 
     101        item2 = self.form.model.item(1) 
     102        item3 = self.form.model.item(2) 
     103        self.assertTrue(item1.checkState() == QtCore.Qt.Checked) 
     104        self.assertTrue(item2.checkState() == QtCore.Qt.Checked) 
     105        self.assertTrue(item3.checkState() == QtCore.Qt.Checked) 
    77106 
    78107        # Click on the delete  button 
     
    80109 
    81110        # Test the warning dialog called once 
    82         # self.assertTrue(QtGui.QFileDialog.getOpenFileName.called) 
     111        self.assertTrue(QtGui.QMessageBox.question.called) 
     112 
     113        # Assure the model still contains the items 
     114        self.assertEqual(self.form.model.rowCount(), 3) 
     115 
     116        # Now, mock the confirmation dialog with return=Yes 
     117        QtGui.QMessageBox.question = MagicMock(return_value=QtGui.QMessageBox.Yes) 
     118 
     119        # Click on the delete  button 
     120        QTest.mouseClick(deleteButton, Qt.LeftButton) 
     121 
     122        # Test the warning dialog called once 
     123        self.assertTrue(QtGui.QMessageBox.question.called) 
    83124 
    84125        # Assure the model contains no items 
    85  
    86     def testSendToButton(self):         
    87         sendToButton = self.form.cmdSendTo 
    88  
    89         # Mock the current perspective set_data method 
    90  
     126        self.assertEqual(self.form.model.rowCount(), 0) 
     127 
     128        # Click delete once again to assure no nasty behaviour on empty model 
     129        QTest.mouseClick(deleteButton, Qt.LeftButton) 
     130 
     131 
     132    def testSendToButton(self): 
     133        """ 
     134        Test that clicking the Send To button sends checked data to a perspective 
     135        """ 
     136         
    91137        # Populate the model 
     138        filename = ["cyl_400_20.txt"] 
     139        self.form.readData(filename) 
     140 
     141        # setData is the method we want to see called 
     142        mocked = self.form.parent.perspective().setData 
     143        mocked = MagicMock(filename) 
    92144 
    93145        # Assure the checkbox is on 
     146        self.form.cbSelect.setCurrentIndex(0) 
    94147 
    95148        # Click on the Send To  button 
    96         QTest.mouseClick(sendToButton, Qt.LeftButton) 
     149        QTest.mouseClick(self.form.cmdSendTo, Qt.LeftButton) 
    97150 
    98151        # Test the set_data method called once 
    99         # self.assertTrue(QtGui.QFileDialog.getOpenFileName.called) 
    100  
    101         # Assure the model contains no items 
     152        # self.assertTrue(mocked.called) 
     153 
     154        # open another file 
     155        filename = ["cyl_400_20.txt"] 
     156        self.form.readData(filename) 
     157 
     158        # Mock the warning message 
     159        QtGui.QMessageBox = MagicMock() 
     160 
     161        # Click on the button 
     162        QTest.mouseClick(self.form.cmdSendTo, Qt.LeftButton) 
     163 
     164        # Assure the message box popped up 
     165        QtGui.QMessageBox.assert_called_once() 
     166 
    102167 
    103168    def testDataSelection(self): 
     
    159224    def testReadData(self): 
    160225        """ 
    161         Test the readData() method 
     226        Test the low level readData() method 
    162227        """ 
    163228        filename = ["cyl_400_20.txt"] 
     
    172237 
    173238        # Expected two status bar updates 
    174         self.assertEqual(spy_status_update.count(), 2) 
     239        self.assertEqual(spy_status_update.count(), 1) 
    175240        self.assertIn(filename[0], str(spy_status_update.called()[0]['args'][0])) 
    176         self.assertIn("Loading Data Complete", str(spy_status_update.called()[1]['args'][0])) 
    177  
    178         # Expect one Data Received signal 
    179         self.assertEqual(spy_data_received.count(), 1) 
    180  
    181         # Assure returned dictionary has correct data 
    182         # We don't know the data ID, so need to iterate over dict 
    183         data_dict = spy_data_received.called()[0]['args'][0] 
    184         for data_key, data_value in data_dict.iteritems(): 
    185             self.assertIsInstance(data_value, Data1D) 
     241 
    186242 
    187243        # Check that the model contains the item 
     
    194250        self.assertEqual(model_name, filename[0]) 
    195251 
    196         # Assure add_data on data_manager was called (last call) 
    197         self.assertTrue(self.form.manager.add_data.called) 
     252    def testLoadFile(self): 
     253        """ 
     254        Test the threaded call to readData() 
     255        """ 
     256        pass 
    198257 
    199258    def testGetWList(self): 
     
    206265            'HFIR 1D files (*.d1d);;DANSE files (*.sans);;NXS files (*.nxs)' 
    207266        self.assertEqual(defaults, list) 
    208  
     267        
    209268    def testLoadComplete(self): 
    210269        """ 
    211         """ 
    212         # Initialize signal spy instances         
     270        Test the callback method updating the data object 
     271        """ 
     272 
     273        data_dict = {"a1":Data1D()} 
     274 
     275        self.form.manager.add_data = MagicMock() 
     276 
     277        # Initialize signal spy instances 
    213278        spy_status_update = QtSignalSpy(self.form, self.form.communicate.statusBarUpdateSignal) 
    214279        spy_data_received = QtSignalSpy(self.form, self.form.communicate.fileDataReceivedSignal) 
    215280 
    216         # Need an empty Data1D object 
    217         mockData = Data1D() 
    218  
    219         # Call the tested method 
    220         self.form.loadComplete({"a":mockData}, message="test message") 
    221  
    222         # test the signals  
    223         self.assertEqual(spy_status_update.count(), 1) 
    224         self.assertIn("message", str(spy_status_update.called()[0]['args'][0])) 
     281        # Read in the file 
     282        self.form.loadComplete(data_dict, message="Loading Data Complete") 
     283 
     284        # "Loading data complete" no longer sent in LoadFile but in callback 
     285        self.assertIn("Loading Data Complete", str(spy_status_update.called()[0]['args'][0])) 
     286 
     287        # Expect one Data Received signal 
    225288        self.assertEqual(spy_data_received.count(), 1) 
    226289 
    227         # The data_manager update is going away, so don't bother testing 
    228         
     290        # Assure returned dictionary has correct data 
     291        # We don't know the data ID, so need to iterate over dict 
     292        data_dict = spy_data_received.called()[0]['args'][0] 
     293        for data_key, data_value in data_dict.iteritems(): 
     294            self.assertIsInstance(data_value, Data1D) 
     295 
     296        # Assure add_data on data_manager was called (last call) 
     297        self.assertTrue(self.form.manager.add_data.called) 
     298 
     299 
    229300if __name__ == "__main__": 
    230301    unittest.main() 
  • src/sas/qtgui/UnitTesting/GuiManagerTest.py

    r488c49d r5032ea68  
    99# Local 
    1010from GuiManager import GuiManager 
     11from UI.MainWindowUI import MainWindow 
    1112 
    12 #app = QApplication(sys.argv) 
     13app = QApplication(sys.argv) 
    1314 
    1415class GuiManagerTest(unittest.TestCase): 
    15     '''Test the WelcomePanel''' 
     16    '''Test the Main Window functionality''' 
    1617    def setUp(self): 
    1718        '''Create the tested object''' 
     19        class MainSasViewWindow(MainWindow): 
     20            # Main window of the application 
     21            def __init__(self, reactor, parent=None): 
     22                super(MainSasViewWindow, self).__init__(parent) 
     23         
     24                # define workspace for dialogs. 
     25                self.workspace = QWorkspace(self) 
     26                self.setCentralWidget(self.workspace) 
    1827 
    19         self.manager = GuiManager(None) 
     28        self.manager = GuiManager(MainSasViewWindow(None), None) 
    2029 
    2130    def tearDown(self): 
     
    5261        pass 
    5362 
     63    def testActionLoadData(self): 
     64        """ 
     65        Menu File/Load Data File(s) 
     66        """ 
     67        # Mock the system file open method 
     68        QFileDialog.getOpenFileName = MagicMock(return_value=None) 
     69 
     70        # invoke the action 
     71 
     72        # Test the getOpenFileName() dialog called once 
     73        #self.assertTrue(QtGui.QFileDialog.getOpenFileName.called) 
     74        #QtGui.QFileDialog.getOpenFileName.assert_called_once() 
     75         
     76 
    5477    # test each action separately 
    5578        
  • src/sas/qtgui/UnitTesting/TestUtilsTest.py

    r488c49d r5032ea68  
    1818    def testQtSignalSpy(self): 
    1919        '''Create the Spy the correct way''' 
     20        test_string = 'my precious' 
    2021 
     22        def signalReceived(signal): 
     23            # Test the signal callback 
     24            self.assertEqual(signal, test_string) 
     25 
     26        communicator = Communicate() 
     27        communicator.statusBarUpdateSignal.connect(signalReceived) 
     28 
     29        # Define the signal spy for testing 
    2130        widget = QWidget() 
    22         signal = Communicate.statusBarUpdateSignal 
    23         self.spy = QtSignalSpy(widget, signal) 
     31        spy = QtSignalSpy(widget, communicator.statusBarUpdateSignal) 
    2432 
    2533        # Emit a signal 
    26         signal.emit('aa') 
     34        communicator.statusBarUpdateSignal.emit(test_string) 
    2735 
    28         # Test the spy object 
     36        # Was the signal caught by the signal spy? 
     37        self.assertEqual(spy.count(), 1) 
    2938 
    3039if __name__ == "__main__": 
  • src/sas/qtgui/run_tests.sh

    r488c49d r5032ea68  
    1 python -m UnitTesting.TestUtilsTest 
    2 python -m UnitTesting.WelcomePanelTest 
     1# python -m UnitTesting.TestUtilsTest 
     2# python -m UnitTesting.WelcomePanelTest 
    33python -m UnitTesting.DataExplorerTest 
    4 python -m UnitTesting.MainWindowTest 
     4# python -m UnitTesting.GuiManagerTest 
     5# python -m UnitTesting.MainWindowTest 
    56 
Note: See TracChangeset for help on using the changeset viewer.