Example #1
0
    def __init__(self, fileName):
        self._app = QApplication.instance().window
        self.preferencesFile = os.path.expanduser(fileName)

        self._widget = QWidget()
        self._ui = Ui_Preferences()
        self._ui.setupUi(self._widget)

        # Load preferences from file
        self.loadPreferencesFile()
        self.resetUi()
        
        # Connect button signals
        self._ui.buttons.button(QDialogButtonBox.Reset).clicked.connect(self.resetUi)
        self._ui.buttons.button(QDialogButtonBox.Save).clicked.connect(self.savePreferences)
        self._ui.buttons.button(QDialogButtonBox.Cancel).clicked.connect(self.hideDialog)
        self._ui.defaultDirectoryButton.clicked.connect(self.defaultDirectorySelector)
        self._ui.projectDirectoryButton.clicked.connect(self.projectDirectorySelector)
        #self._ui.textOptions.clicked.connect(self._ui.textOptions.showTextOptionsDialog)

        self._window = SubWindow(self._app.ui.workspace)
        self._window.setWidget(self._widget)
        self._widget.setParent(self._window)

        self._window.hide()
    def load(self):
        self.window = SubWindow(self._app.ui.workspace)

        self.menuEntry = QAction(self._app)
        self.menuEntry.setObjectName("actionManageWavesWidget")
        self.menuEntry.setText("Manage Waves")
        self.menuEntry.triggered.connect(self.window.show)
        self.menu = vars(self._app.ui)["menuData"]
        self.menu.addAction(self.menuEntry)

        self.buildWidget()
        self.window.setWidget(self._widget)
        self._widget.setParent(self.window)

        self.window.hide()
    def load(self):
        self.window = SubWindow(self._app.ui.workspace)

        self.menuEntry = QAction(self._app)
        self.menuEntry.setObjectName("actionNewTableDialog")
        self.menuEntry.setShortcut("Ctrl+T")
        self.menuEntry.setText("New Table")
        self.menuEntry.triggered.connect(self.window.show)
        self.menu = vars(self._app.ui)["menuNew"]
        self.menu.addAction(self.menuEntry)

        self.buildWidget()
        self.window.setWidget(self._widget)
        self._widget.setParent(self.window)

        self.window.hide()
    def __init__(self, *args):
        QPushButton.__init__(self, *args)
        self._app = QApplication.instance().window

        self.textOptions = Property.TextOptions()

        # Set initial textOptions values based on application preferences
        if 'preferences' in self._app.__dict__.keys():
            preferenceTextOptions = self._app.preferences.getInternal('textOptions')
            if preferenceTextOptions is not None:
                self.setTextOptions(preferenceTextOptions)

        # Create dialog
        self._dialog = QTextOptionsWidget(self)
        self._ui = Ui_TextOptionsWidget()
        self._ui.setupUi(self._dialog)
        self._dialog.setUiObject(self._ui)

        self._window = SubWindow(self._app.ui.workspace)
        self._window.setWidget(self._dialog)
        self._dialog.setParent(self._window)
        
        self.hideTextOptionsWidget()
        
        # Create font list from matplotlib fonts
        fontPropList = fm.createFontList(fm.findSystemFonts())
        
        # Get just the names
        fontList = map(lambda x:x.name, fontPropList)
        
        # Unique and sort the names
        fontList = list(set(fontList))
        fontList.sort()
        
        # Enter fonts into the ui widget
        self._ui.name.addItems(fontList)
Example #5
0
    def load(self):
        self.window = SubWindow(self._app.ui.workspace)

        self.menuEntry = QAction(self._app)
        self.menuEntry.setObjectName("actionImportCSV")
        self.menuEntry.setShortcut("Ctrl+I")
        self.menuEntry.setText("Import CSV Data")
        self.menuEntry.triggered.connect(self.window.show)
        
        # Check if menu already exists
        if "menuImport" not in vars(self._app.ui).keys():
            self._app.ui.menuImport = QMenu(self._app.ui.menuFile)
            self._app.ui.menuImport.setObjectName("menuImport")
            self._app.ui.menuImport.setTitle(QApplication.translate("MainWindow", "Import", None, QApplication.UnicodeUTF8))
            self._app.ui.menuFile.addAction(self._app.ui.menuImport.menuAction())
        
        self.menu = vars(self._app.ui)["menuImport"]
        self.menu.addAction(self.menuEntry)

        self.buildWidget()
        self.window.setWidget(self._widget)
        self._widget.setParent(self.window)

        self.window.hide()
Example #6
0
class CurveFitting(Module):
    """Module to fit waves to a function."""

    # To add new fitting functions, do the following:
    # 1) Edit GUI
    # 2) Modify _parameterTableDefaults
    # 3) Create a fit[Function] method to do the fitting
    # 4) Call fit[Function] from doFit

    # Default values for parameter table
    # Each dict entry is a list of lists. The inner list contains a row of values.
    _parameterTableDefaults = {
            'Polynomial': [
                ['p0', 1],
                ['p1', 1],
                ['p2', 1],
                ],
            'Sinusoid': [
                ['p0', 1],
                ['p1', 1],
                ['p2', 1],
                ['p3', 1],
                ],
            'Power Law': [
                ['y0', 0],
                ['a', 1],
                ['k', 1],
                ],
            'Exponential': [
                ['y0', 0],
                ['A', 1],
                ['b', 1],
                ],
            'Logarithm': [
                ['y0', 0],
                ['a', 1],
                ['base', 10],
                ],
            'Gaussian': [
                ['amp', 1],
                ['mean', 0],
                ['width', 1],
                ],
            'Lorentzian': [
                ['amp', 1],
                ['mean', 0],
                ['hwhm', 1],
                ],
            }

    def __init__(self):
        Module.__init__(self)

    def buildWidget(self):
        self._widget = QWidget()
        self._ui = Ui_CurveFitting()
        self._ui.setupUi(self._widget)

        self.setModels()
        self.setupSpinBoxes()
        self.setupParameterTableData()

        # Connect button signals
        self._ui.doFitButton.clicked.connect(self.doFit)
        self._ui.closeButton.clicked.connect(self.closeWindow)
        self._ui.function.currentIndexChanged[str].connect(self.changeFunction)
        self._ui.function.currentIndexChanged[str].connect(self.connectSlotsOnFunctionChange)
        self._ui.initialValuesWave.activated[str].connect(self.changeInitialValuesWave)
        self._ui.useInitialValuesWave.toggled[bool].connect(self.changeInitialValuesWaveFromCheckbox)

        self._ui.useWaveForInterpolation.toggled[bool].connect(self.catchInterpolationWaveGroupBoxCheck)
        self._ui.useDomainForInterpolation.toggled[bool].connect(self.catchInterpolationDomainGroupBoxCheck)
        
        self.connectSlotsOnFunctionChange('')

    def setModels(self):
        # Set up model and view
        self._allWavesListModel = self._app.model('appWaves')
        self._ui.xWave.setModel(self._allWavesListModel)
        self._ui.yWave.setModel(self._allWavesListModel)
        self._ui.weightWave.setModel(self._allWavesListModel)
        self._ui.initialValuesWave.setModel(self._allWavesListModel)
        self._ui.interpolationWave.setModel(self._allWavesListModel)

    def setupSpinBoxes(self):
        self._ui.dataRangeStart.addWaveView(self._ui.xWave)
        self._ui.dataRangeEnd.addWaveView(self._ui.xWave)

        self._ui.dataRangeStart.addWaveView(self._ui.yWave)
        self._ui.dataRangeEnd.addWaveView(self._ui.yWave)

        self._ui.interpolationWaveRangeStart.addWaveView(self._ui.interpolationWave)
        self._ui.interpolationWaveRangeEnd.addWaveView(self._ui.interpolationWave)

    def setupParameterTableData(self):
        self._parameterTableData = {}
        self._currentFunction = None
        self.changeFunction('')
    
    def closeWindow(self):
        self._widget.parent().close()

    def connectSlotsOnFunctionChange(self, newFunctionName):
        """
        Disconnect slots dependent on which function is chosen.

        If polynomial function is chosen, connect slot to update parameter table
        on degree change.
        """

        # Disconnect slots
        try:
            self._ui.polynomialDegree.valueChanged[int].disconnect(self.changePolynomialDegree)
        except:
            pass

        # Connect polynomial degree change
        if Util.getWidgetValue(self._ui.function) == 'Polynomial':
            self._ui.polynomialDegree.valueChanged[int].connect(self.changePolynomialDegree)

    def catchInterpolationWaveGroupBoxCheck(self, checked):
        # Set the opposite check for the domain group box
        Util.setWidgetValue(self._ui.useDomainForInterpolation, not checked)

    def catchInterpolationDomainGroupBoxCheck(self, checked):
        # Set the opposite check for the wave group box
        Util.setWidgetValue(self._ui.useWaveForInterpolation, not checked)
        
    def saveParameterTable(self):
        """
        Save the parameters for the current function to the object.
        """

        if self._currentFunction:
            self._parameterTableData[self._currentFunction] = self.getCurrentParameterTable()

    def changeFunction(self, newFunctionName):
        # Save parameters for old function
        self.saveParameterTable()
        #if self._currentFunction:
        #    self._parameterTableData[self._currentFunction] = self.getCurrentParameterTable()

        # Now update _currentFunction to the function that is currently selected.
        # If this method was called because the user selected a different function,
        # then this will be modified. If it was called because the fit curve button
        # was pressed, then its value will not be changed.
        self._currentFunction = Util.getWidgetValue(self._ui.function)

        # Enter in parameters for new function
        # If there are previously user-entered values, then use them
        # else, if a wave is selected, then use that
        # else, use the initial values
        # Either way, if there are blank entries, then use initial values for them

        # Clear the table, but leave all the column headers
        for rowIndex in range(self._ui.parameterTable.rowCount()):
            self._ui.parameterTable.removeRow(0)

        parameters = []
        # If there is saved data, use it
        if self._currentFunction in self._parameterTableData:
            parameters = self._parameterTableData[self._currentFunction]

        # If there aren't enough rows for all the parameters, extend with 
        # initial values. This will also occur if no parameters had been saved.
        savedParametersLength = len(parameters)
        defaultParameters = self._parameterTableDefaults[self._currentFunction]
        if savedParametersLength < len(defaultParameters):
            parameters.extend(defaultParameters[len(parameters):])
            
            # Use wave if requested by the user
            if Util.getWidgetValue(self._ui.useInitialValuesWave):
                # Convert from QString to str
                waveName = str(Util.getWidgetValue(self._ui.initialValuesWave))
                if self._app.waves().wave(waveName) is None:
                    # waveName is not a name of a wave
                    pass
                else:
                    waveData = self._app.waves().wave(waveName).data()
                    for i in range(savedParametersLength, len(defaultParameters)):
                        parameters[i][1] = waveData[i]
        
        self.writeParametersToTable(parameters)

    def writeParametersToTable(self, parameters, startRow=0):
        # Determine how many rows the table should have
        numRows = startRow + len(parameters)
        self._ui.parameterTable.setRowCount(numRows)

        # Now actually write to the table
        for rowIndex, row in enumerate(parameters, startRow):
            for colIndex, value in enumerate(row):
                item = QTableWidgetItem(str(value))
                if colIndex == 0:
                    # parameter name, do not want it editable
                    item.setFlags(Qt.ItemIsEnabled)

                self._ui.parameterTable.setItem(rowIndex, colIndex, item)
                
    def changePolynomialDegree(self, newDegree):
        # If decreasing the degree, just remove the last entries
        # If increasing the degree,
        #    If a wave is selected, then use that for the new values
        #    else, use the initial values
        
        desiredNumRows = newDegree + 1
        currentNumRows = self._ui.parameterTable.rowCount()
        
        if desiredNumRows == currentNumRows:
            # Nothing to do
            return

        # Set defaults
        rows = []
        for d in range(desiredNumRows):
            rows.append(['p' + str(d), 1])
        self._parameterTableDefaults['Polynomial'] = rows

        # Update table
        self._ui.parameterTable.setRowCount(desiredNumRows)

        if desiredNumRows < currentNumRows:
            # We are done, because no rows need to be edited
            return

        # Degree is being increased
        parameters = self._parameterTableDefaults['Polynomial'][currentNumRows:desiredNumRows]
        if Util.getWidgetValue(self._ui.useInitialValuesWave):
            # Convert from QString to str
            waveName = str(Util.getWidgetValue(self._ui.initialValuesWave))
            if self._app.waves().wave(waveName) is None:
                # waveName is not a name of a wave
                pass
            else:
                waveData = self._app.waves().wave(waveName).data(currentNumRows, desiredNumRows)
                for index, value in enumerate(waveData):
                    parameters[index][1] = value

        self.writeParametersToTable(parameters, currentNumRows)

    def changeInitialValuesWaveFromCheckbox(self, checked):
        """
        If the useInitialValuesWave checkbox is checked, then
        call changeInitialValuesWave.
        """

        if checked:
            self.changeInitialValuesWave(str(Util.getWidgetValue(self._ui.initialValuesWave)))

    def changeInitialValuesWave(self, waveName):
        # Use the wave for as many parameters as possible
        # if the wave is too long, then just use the first n values
        # if the wave is too short, then leave the current value in place
        #     if there is no current value, then use the initial values

        if Util.getWidgetValue(self._ui.useInitialValuesWave):
            # Get the current values, with any undefined values using the initial values
            parameters = self.currentParametersBackedByDefaults()

            # Now get the wave values
            parameters = self.updateParametersListWithWave(parameters, waveName)

            # Set the table to the parameters
            self.writeParametersToTable(parameters)


    def updateParametersListWithWave(self, parameters, waveName):
        """
        Given a list of parameter table rows, and the name of a wave,
        this will update the parameter values with the entries in the wave.
        """

        waveName = str(waveName)
        if self._app.waves().wave(waveName) is None:
            # waveName is not a name of a wave
            return parameters

        waveData = self._app.waves().wave(waveName).data(0, len(parameters))
        for i in range(len(waveData)):
            parameters[i][1] = waveData[i]

        return parameters

    def currentParametersBackedByDefaults(self):
        # Start with initial values
        parameters = self._parameterTableDefaults[self._currentFunction]

        # Then get the current values and update parameters with it
        currentParameters = self.getCurrentParameterTable()
        for rowIndex, row in enumerate(currentParameters):
            parameters[rowIndex] = row

        return parameters

    def getCurrentParameterTable(self):
        """
        Save data to a 2-d array mimicking the table.
        """
        # FIXME only works with text right now. Need to add in support for check boxes
        # Maybe do this by creating a QTableWidgetItem option in Util.getWidgetValue
        # and using QTableWidget.cellWidget to get the indiv. cells
        table = []
        row = []
        for rowIndex in range(self._ui.parameterTable.rowCount()):
            for colIndex in range(self._ui.parameterTable.columnCount()):
                try:
                    row.append(str(self._ui.parameterTable.item(rowIndex, colIndex).text()))
                except AttributeError:
                    row.append('')
            table.append(row)
            row = []

        return table

    def parameterColumnValues(self, functionName, columnNum):
        """
        Return a list of the values of a specific column in the parameter table.
        """

        if functionName not in self._parameterTableData:
            return None

        tableData = self._parameterTableData[functionName]

        values = [str(row[columnNum]) for row in tableData]
        return values

    def parameterNames(self, functionName):
        """
        Return a list of the names of the parameters for the given function.
        """
        return self.parameterColumnValues(functionName, 0)

    def parameterInitialValues(self, functionName):
        """
        Return a list of the initial values of the parameters (NOT the default values) for the given function.
        """
        values = self.parameterColumnValues(functionName, 1)
        initialValues = [float(v) if Util.isNumber(v) else 1 for v in values]
        return initialValues

    def doFit(self):
        # save user-defined parameters
        self.saveParameterTable()

        # Get all waves that are selected before doing anything else
        # If any waves are created, as they are in the output tab section,
        # then the wave combo boxes are refreshed, and the previous selection
        # is lost
        xWaveName = Util.getWidgetValue(self._ui.xWave)
        yWaveName = Util.getWidgetValue(self._ui.yWave)
        weightWaveName = Util.getWidgetValue(self._ui.weightWave)
        interpolationDomainWaveName = Util.getWidgetValue(self._ui.interpolationWave)

        # Get data tab
        dataRangeStart = Util.getWidgetValue(self._ui.dataRangeStart)
        dataRangeEnd = Util.getWidgetValue(self._ui.dataRangeEnd)
        
        xWave = self._app.waves().wave(xWaveName)
        yWave = self._app.waves().wave(yWaveName)
        xLength = xWave.length()
        yLength = yWave.length()
        
        # Verify data range limits are valid
        if dataRangeStart > xLength or dataRangeStart > yLength:
            dataRangeStart = 0
        if dataRangeEnd > xLength or dataRangeEnd > yLength:
            dataRangeEnd = min(xLength, yLength) - 1

        xData = xWave.data(dataRangeStart, dataRangeEnd + 1)
        yData = yWave.data(dataRangeStart, dataRangeEnd + 1)

        # Get weights, if required by user
        if Util.getWidgetValue(self._ui.useWeights):
            weightWave = self._app.waves().wave(weightWaveName)
            weightLength = weightWave.length()
            weightData = weightWave.data(dataRangeStart, dataRangeEnd + 1)

            # If weighting inversely, invert the weights
            if Util.getWidgetValue(self._ui.weightIndirectly):
                weightData = [1./w if w != 0 else 0 for w in weightData]

            if len(weightData) != len(yData):
                print "The number of weight points is not the same as the number of y points."
                return 1
        else:
            weightData = None

        # Get output tab
        outputOptions = {}
        outputWaves = {}

        outputOptions['createTable'] = Util.getWidgetValue(self._ui.createTable)

        outputOptions['outputParameters'] = Util.getWidgetValue(self._ui.outputParameters)
        if outputOptions['outputParameters']:
            outputOptions['saveLabels'] = Util.getWidgetValue(self._ui.saveLabels)

            # Create saveLabels wave
            if outputOptions['saveLabels']:
                saveLabelsDestination = self._app.waves().findGoodWaveName(Util.getWidgetValue(self._ui.saveLabelsDestination))
                outputWaves['saveLabelsWave'] = Wave(saveLabelsDestination, 'String')
                self._app.waves().addWave(outputWaves['saveLabelsWave'])

            # Create parameter wave
            parameterDestination = self._app.waves().findGoodWaveName(Util.getWidgetValue(self._ui.parameterDestination))
            outputWaves['parameterWave'] = Wave(parameterDestination, 'Decimal')
            self._app.waves().addWave(outputWaves['parameterWave'])

        outputOptions['outputInterpolation'] = Util.getWidgetValue(self._ui.outputInterpolation)
        if outputOptions['outputInterpolation']:
            # Create interpolation wave
            interpolationDestination = self._app.waves().findGoodWaveName(Util.getWidgetValue(self._ui.interpolationDestination))
            outputWaves['interpolationDestinationWave'] = Wave(interpolationDestination, 'Decimal')
            self._app.waves().addWave(outputWaves['interpolationDestinationWave'])

            if Util.getWidgetValue(self._ui.useWaveForInterpolation):
                # Using an already-existing wave for the interpolation points.
                interpolationDomainWave = self._app.waves().wave(interpolationDomainWaveName)
                interpolationWaveRangeStart = Util.getWidgetValue(self._ui.interpolationWaveRangeStart)
                interpolationWaveRangeEnd = Util.getWidgetValue(self._ui.interpolationWaveRangeEnd)
                
                outputWaves['interpolationDomainWave'] = interpolationDomainWave
    
                # Start the wave with as many blanks as necessary in order to get the destination wave
                # to line up correctly with the domain wave, for easy plotting.
                outputWaves['interpolationDestinationWave'].extend([''] * interpolationWaveRangeStart)
                
                # Verify data range limits are valid
                interpolationDomainLength = interpolationDomainWave.length()
                if interpolationWaveRangeStart > interpolationDomainLength:
                    interpolationWaveRangeStart = 0
                if interpolationWaveRangeEnd > interpolationDomainLength:
                    interpolationWaveRangeEnd  = interpolationDomainLength - 1
    
                outputOptions['interpolationDomainWaveData'] = interpolationDomainWave.data(interpolationWaveRangeStart, interpolationWaveRangeEnd + 1)
            else:
                # Creating a new wave based on a domain and number of points.
                customWaveName = Util.getWidgetValue(self._ui.interpolationCustomWaveName)
                customLowerLimit = float(Util.getWidgetValue(self._ui.interpolationCustomLowerLimit))
                customUpperLimit = float(Util.getWidgetValue(self._ui.interpolationCustomUpperLimit))
                customNumPoints = Util.getWidgetValue(self._ui.interpolationCustomNumPoints)

                outputOptions['interpolationDomainWaveData'] = numpy.linspace(customLowerLimit, customUpperLimit, customNumPoints, endpoint=True)

                interpolationDomainWaveName = self._app.waves().findGoodWaveName(customWaveName)
                outputWaves['interpolationDomainWave'] = Wave(interpolationDomainWaveName, 'Decimal', outputOptions['interpolationDomainWaveData'])
                self._app.waves().addWave(outputWaves['interpolationDomainWave'])

        outputOptions['saveResiduals'] = Util.getWidgetValue(self._ui.saveResiduals)
        if outputOptions['saveResiduals']:
            residualsDestination = self._app.waves().findGoodWaveName(Util.getWidgetValue(self._ui.residualsDestination))
            outputWaves['residualsWave'] = Wave(residualsDestination, 'Decimal')
            self._app.waves().addWave(outputWaves['residualsWave'])

            # If the fit is not done to all the data in the wave, then we need to add blanks to the beginning
            # of the residual wave because the residuals will only be calculated for the part of the data that
            # was actually fit.
            outputWaves['residualsWave'].extend([''] * dataRangeStart)

            # Save the x wave, in case it is different from the interpolationDomainWave
            outputWaves['xWave'] = xWave

        # Determine the function and call the appropriate method
        functionName = Util.getWidgetValue(self._ui.function)

        if functionName == 'Polynomial':
            self.fitPolynomial(xData, yData, weightData, outputWaves, outputOptions)
        elif functionName == 'Sinusoid':
            self.fitSinusoid(xData, yData, weightData, outputWaves, outputOptions)
        elif functionName == 'Power Law':
            self.fitPowerLaw(xData, yData, weightData, outputWaves, outputOptions)
        elif functionName == 'Exponential':
            self.fitExponential(xData, yData, weightData, outputWaves, outputOptions)
        elif functionName == 'Logarithm':
            self.fitLogarithm(xData, yData, weightData, outputWaves, outputOptions)
        elif functionName == 'Gaussian':
            self.fitGaussian(xData, yData, weightData, outputWaves, outputOptions)
        elif functionName == 'Lorentzian':
            self.fitLorentzian(xData, yData, weightData, outputWaves, outputOptions)

    def fitPolynomial(self, xData, yData, weightData=None, outputWaves={}, outputOptions={}):
        # Get the degree of the polynomial the user wants to use
        degree = Util.getWidgetValue(self._ui.polynomialDegree)

        def polynomialFunction(p, x):
            # If x is a list, then val needs to be a list
            # If x is a number, then val needs to be a number
            if isinstance(x, list):
                val = numpy.array([p[0]] * len(x))
            else:
                val = p[0]

            # Add x, x^2, x^3, etc entries
            for d in range(1, degree + 1):
                val += numpy.multiply(p[d], numpy.power(x, d))
            return val

        parameterNames = self.parameterNames('Polynomial')
        initialValues = self.parameterInitialValues('Polynomial')
        if initialValues is None:
            initialValues = [1] * degree

        self.fitFunction(polynomialFunction, parameterNames, initialValues, xData, yData, weightData, outputWaves, outputOptions, 'Polynomial Fit')

    def fitSinusoid(self, xData, yData, weightData=None, outputWaves={}, outputOptions={}):
        sinusoidFunction = lambda p, x: p[0] + p[1] * numpy.cos(x / p[2] * 2. * numpy.pi + p[3])
        
        parameterNames = self.parameterNames('Sinusoid')
        initialValues = self.parameterInitialValues('Sinusoid')
        if initialValues is None:
            initialValues = [1, 1, 1, 1]

        self.fitFunction(sinusoidFunction, parameterNames, initialValues, xData, yData, weightData, outputWaves, outputOptions, 'Sinusoid Fit')

    def fitPowerLaw(self, xData, yData, weightData=None, outputWaves={}, outputOptions={}):
        powerLawFunction = lambda p, x: numpy.add(p[0], numpy.multiply(p[1], numpy.power(x, p[2])))

        parameterNames = self.parameterNames('Power Law')
        initialValues = self.parameterInitialValues('Power Law')
        if initialValues is None:
            initialValues = [0, 1, 1]

        self.fitFunction(powerLawFunction, parameterNames, initialValues, xData, yData, weightData, outputWaves, outputOptions, 'Power Law Fit')

    def fitExponential(self, xData, yData, weightData=None, outputWaves={}, outputOptions={}):
        exponentialFunction = lambda p, x: numpy.add(p[0], numpy.multiply(p[1], numpy.power(numpy.e, numpy.multiply(p[2], x))))

        parameterNames = self.parameterNames('Exponential')
        initialValues = self.parameterInitialValues('Exponential')
        if initialValues is None:
            initialValues = [0, 1, 1]

        self.fitFunction(exponentialFunction, parameterNames, initialValues, xData, yData, weightData, outputWaves, outputOptions, 'Exponential Fit')

    def fitLogarithm(self, xData, yData, weightData=None, outputWaves={}, outputOptions={}):
        # There is no numpy log function where you can specify a custom base, so we'll define one
        customBaseLog = lambda base, x: numpy.divide(numpy.log(x), numpy.log(base))
        logarithmFunction = lambda p, x: numpy.add(p[0], numpy.multiply(p[1], customBaseLog(p[2], x)))

        parameterNames = self.parameterNames('Logarithm')
        initialValues = self.parameterInitialValues('Logarithm')
        if initialValues is None:
            initialValues = [0, 1, 10]

        self.fitFunction(logarithmFunction, parameterNames, initialValues, xData, yData, weightData, outputWaves, outputOptions, 'Logarithm Fit')

    def fitGaussian(self, xData, yData, weightData=None, outputWaves={}, outputOptions={}):
        gaussianFunction = lambda p, x: numpy.multiply(p[0], numpy.power(numpy.e, numpy.divide(-1 * numpy.power((numpy.subtract(x, p[1])), 2), 2 * numpy.power(p[2], 2))))

        parameterNames = self.parameterNames('Gaussian')
        initialValues = self.parameterInitialValues('Gaussian')
        if initialValues is None:
            initialValues = [1, 0, 1]

        self.fitFunction(gaussianFunction, parameterNames, initialValues, xData, yData, weightData, outputWaves, outputOptions, 'Gaussian Fit')

    def fitLorentzian(self, xData, yData, weightData=None, outputWaves={}, outputOptions={}):
        lorentzianFunction = lambda p, x: numpy.divide(numpy.multiply(p[0], p[2]), numpy.add(numpy.power(numpy.subtract(x, p[1]), 2), numpy.power(p[2], 2)))

        parameterNames = self.parameterNames('Lorentzian')
        initialValues = self.parameterInitialValues('Lorentzian')
        if initialValues is None:
            initialValues = [1, 0, 1]

        self.fitFunction(lorentzianFunction, parameterNames, initialValues, xData, yData, weightData, outputWaves, outputOptions, 'Lorentzian Fit')



    def fitFunction(self, function, parameterNames, initialValues, xData, yData, weightData=None, outputWaves={}, outputOptions={}, tableName='Fit'):
        # Can also include initial guesses for the parameters, as well as sigma's for weighting of the ydata

        # Need to fail with error message if the leastsq call does not succeed

        # Do the fit
        result = self.fitFunctionLeastSquares(function, initialValues, xData, yData, weightData)
        parameters = result[0]

        tableWaves = []

        # Deal with the parameter-related waves
        if outputOptions['outputParameters']:
            # save parameter labels
            if outputOptions['saveLabels']:
                tableWaves.append(outputWaves['saveLabelsWave'])

                outputWaves['saveLabelsWave'].extend(parameterNames)

            tableWaves.append(outputWaves['parameterWave'])

            # save parameters to a wave
            outputWaves['parameterWave'].extend(parameters)

        # Do the interpolation
        if outputOptions['outputInterpolation']:
            domain = outputOptions['interpolationDomainWaveData']
            
            determinedFunction = lambda x: function(parameters, x)
            for val in domain:
                outputWaves['interpolationDestinationWave'].push(determinedFunction(val))

            tableWaves.append(outputWaves['interpolationDomainWave'])
            tableWaves.append(outputWaves['interpolationDestinationWave'])

        # Do the residuals
        if outputOptions['saveResiduals']:
            residualsFunc = lambda p, x, y: numpy.subtract(y, function(p, x))
            residuals = residualsFunc(parameters, xData, yData)
            outputWaves['residualsWave'].extend(residuals)
            tableWaves.append(outputWaves['xWave'])
            tableWaves.append(outputWaves['residualsWave'])

        # Create table
        if outputOptions['createTable']:
            self.createTable(tableWaves, tableName)

    def fitFunctionLeastSquares(self, func, guess, xData, yData, weightData=None):
        """
        Do a least squares fit for a generic function.

        func must have the signature (p, x) where p is a list of parameters
        and x is a float.

        guess is the user's guess of the parameters, and must be a list of 
        length len(p).

        xData and yData are the data to fit.
        """

        if weightData is None:
            #errorFunc = lambda p, x, y: func(p, x) - y
            errorFunc = lambda p, x, y: numpy.subtract(func(p, x), y)
            return scipy.optimize.leastsq(errorFunc, guess[:], args=(xData, yData), full_output=True)
        else:
            errorFunc = lambda p, x, y, w: numpy.multiply(w, numpy.subtract(func(p, x), y))
            return scipy.optimize.leastsq(errorFunc, guess[:], args=(xData, yData, weightData), full_output=True)

        #return scipy.optimize.leastsq(errorFunc, guess[:], args=(xData, yData), full_output=True)


    def createTable(self, waves=[], title='Fit'):
        if len(waves) == 0:
            return

        self._app.createTable(waves, title)



    def load(self):
        self.window = SubWindow(self._app.ui.workspace)

        self.menuEntry = QAction(self._app)
        self.menuEntry.setObjectName("actionCurveFiting")
        self.menuEntry.setText("Curve Fitting")
        self.menuEntry.triggered.connect(self.window.show)
        
        self.menu = vars(self._app.ui)["menuData"]
        self.menu.addAction(self.menuEntry)

        self.buildWidget()
        self.window.setWidget(self._widget)
        self._widget.setParent(self.window)

        self.window.hide()

    def unload(self):

        self._widget.deleteLater()
        self.window.deleteLater()
        self.menu.removeAction(self.menuEntry)

    def reload(self):
        self.setModels()
        self.setupSpinBoxes()
        self.setupParameterTableData()
        Util.setWidgetValue(self._ui.function, 'Polynomial')
        Util.setWidgetValue(self._ui.useInitialValuesWave, False)
Example #7
0
class Preferences():
    """
    Store preferences locally for easy programmatic access.

    Persistant copy of preferences will be stored in a file and loaded upon startup.
    User can modify preferences by opening UI, editing preferences, and saving them.
    By saving the preferences, the object will be updated to reflect the UI, and the
    file will also be updated.
    """

    # These are the defaults for each preference. They will be overwritten
    # by the user's preferences at startup.
    prefs = {
            'projectDirectory': Property.String('~/'),   # Dir for where to store project files
            'defaultDirectory': Property.String('~/'),   # Dir for other purposes
            'textOptions':      Property.TextOptions(),  # Default text options
            }

    def __init__(self, fileName):
        self._app = QApplication.instance().window
        self.preferencesFile = os.path.expanduser(fileName)

        self._widget = QWidget()
        self._ui = Ui_Preferences()
        self._ui.setupUi(self._widget)

        # Load preferences from file
        self.loadPreferencesFile()
        self.resetUi()
        
        # Connect button signals
        self._ui.buttons.button(QDialogButtonBox.Reset).clicked.connect(self.resetUi)
        self._ui.buttons.button(QDialogButtonBox.Save).clicked.connect(self.savePreferences)
        self._ui.buttons.button(QDialogButtonBox.Cancel).clicked.connect(self.hideDialog)
        self._ui.defaultDirectoryButton.clicked.connect(self.defaultDirectorySelector)
        self._ui.projectDirectoryButton.clicked.connect(self.projectDirectorySelector)
        #self._ui.textOptions.clicked.connect(self._ui.textOptions.showTextOptionsDialog)

        self._window = SubWindow(self._app.ui.workspace)
        self._window.setWidget(self._widget)
        self._widget.setParent(self._window)

        self._window.hide()


    def getInternal(self, variable):
        try:
            return self.prefs[variable].get()
        except:
            return None

    def getUi(self, variable):
        return Util.getWidgetValue(vars(self._ui)[variable])

    def setInternal(self, variable, value):
        return self.prefs[variable].set(value)
    
    def setUi(self, variable, value):
        return Util.setWidgetValue(vars(self._ui)[variable], value)

    def showDialog(self):
        self.resetUi()
        self._window.show()

    def hideDialog(self):
        self.resetUi()
        self._window.hide()

    def loadPreferencesFile(self):
        if os.path.isfile(self.preferencesFile):
            with open(self.preferencesFile, 'r') as fileHandle:
                self.prefs = pickle.load(fileHandle)
            
    def uiToInternal(self):
        """
        Copy the UI pref values to the internal prefs dict.
        """
        for pref in self.prefs.keys():
            self.setInternal(pref, self.getUi(pref))

    def resetUi(self):
        """
        Update UI to the current internal cache of preferences.
        """

        for pref in self.prefs.keys():
            self.setUi(pref, self.getInternal(pref))

    def savePreferences(self):
        """
        Save preferences to file and then read file to internal cache.
        Close dialog afterwards.
        """
        
        self.uiToInternal()
        self.writePreferences()
        self.loadPreferencesFile()
        self.hideDialog()

    def writePreferences(self):
        if os.path.isfile(self.preferencesFile) or not os.path.exists(self.preferencesFile):
            with open(self.preferencesFile, 'wb') as fileHandle:
                pickle.dump(self.prefs, fileHandle)



    # Methods for specific preferences
    def defaultDirectorySelector(self):
        directory = QFileDialog.getExistingDirectory(self._app.ui.workspace, "Select Default Directory", Util.getWidgetValue(self._ui.defaultDirectory))
        if os.path.isdir(directory):
            return self.setUi('defaultDirectory', directory)
        return False

    def projectDirectorySelector(self):
        directory = QFileDialog.getExistingDirectory(self._app.ui.workspace, "Select Project Directory", Util.getWidgetValue(self._ui.projectDirectory))
        if os.path.isdir(directory):
            return self.setUi('projectDirectory', directory)
        return False
class ManageWavesWidget(Module):
    """Module to display the Manage Waves dialog window."""

    def __init__(self):
        Module.__init__(self)

    def buildWidget(self):
        """Create the widget and populate it."""

        # Create enclosing widget and UI
        self._widget = QWidget()
        self._ui = Ui_ManageWavesWidget()
        self._ui.setupUi(self._widget)

        self.setModels()

        # Connect some slots
        self._ui.copyWaveOriginalWave.activated.connect(self.resetCopyWaveLimits)
        self._ui.createWaveButton.clicked.connect(self.createWave)
        self._ui.functionInsertWaveButton.clicked.connect(self.insertWaveIntoFunction)

        self._ui.modifyWave_selectWave.selectionModel().currentChanged.connect(self.updateModifyWaveUi)
        self._ui.removeWaveButton.clicked.connect(self.removeWave)
        self._ui.resetWaveButton.clicked.connect(self.updateModifyWaveUi)
        self._ui.modifyWaveButton.clicked.connect(self.modifyWave)

        # Make sure selection list and stack are aligned
        self._ui.waveDataSelectionList.setCurrentRow(0)
        self._ui.waveDataStack.setCurrentIndex(0)

        return self._widget

    def setModels(self):
        # Set up model and views
        self._wavesListModel = self._app.model("appWaves")
        self._ui.copyWaveOriginalWave.setModel(self._wavesListModel)
        self._ui.functionInsertWave.setModel(self._wavesListModel)
        self._ui.modifyWave_selectWave.setModel(self._wavesListModel)

    ####
    # Create Wave tab
    ####
    def createWave(self):
        """
        Create the wave, using whatever starting point (basic, copy, function, etc) is necessary.
        """

        # Check if the wave is unique in the application
        if not self._app.waves().goodWaveName(Util.getWidgetValue(self._ui.createWave_waveName)):
            warningMessage = QMessageBox()
            warningMessage.setWindowTitle("Error!")
            warningMessage.setText("The name you chose has already been used. Please enter a new name.")
            warningMessage.setIcon(QMessageBox.Critical)
            warningMessage.setStandardButtons(QMessageBox.Ok)
            warningMessage.setDefaultButton(QMessageBox.Ok)
            result = warningMessage.exec_()
            return False

        wave = Wave(
            Util.getWidgetValue(self._ui.createWave_waveName), Util.getWidgetValue(self._ui.createWave_dataType)
        )

        # Check how the wave should be initially populated
        initialWaveDataTab = self._ui.waveDataStack.currentWidget().objectName()

        if initialWaveDataTab == "basicTab":
            # Basic wave. Need to determine the type
            basicWaveType = Util.getWidgetValue(self._ui.basicWaveType)
            if basicWaveType == "Blank":
                pass
            elif basicWaveType == "Index (starting at 0)":
                basicWaveLength = Util.getWidgetValue(self._ui.basicWaveLength)
                wave.extend(range(0, basicWaveLength))
            elif basicWaveType == "Index (starting at 1)":
                basicWaveLength = Util.getWidgetValue(self._ui.basicWaveLength)
                wave.extend(range(1, basicWaveLength + 1))

        elif initialWaveDataTab == "copyTab":
            # Copy the data from another wave
            originalWave = (
                self._ui.copyWaveOriginalWave.model()
                .index(self._ui.copyWaveOriginalWave.currentIndex(), 0)
                .internalPointer()
            )
            startingIndex = Util.getWidgetValue(self._ui.copyWaveStartingIndex)
            endingIndex = Util.getWidgetValue(self._ui.copyWaveEndingIndex)
            wave.extend(originalWave.data(startingIndex, endingIndex))

        elif initialWaveDataTab == "functionTab":
            waveLength = Util.getWidgetValue(self._ui.functionWaveLength)
            functionString = Util.getWidgetValue(self._ui.functionEquation)
            data = self.parseFunction(waveLength, functionString)
            wave.extend(data)

        # Add wave to application
        self._app.waves().addWave(wave)

        # Reset certain ui fields
        self._ui.copyWaveOriginalWave.setCurrentIndex(0)
        self._ui.functionInsertWave.setCurrentIndex(0)

    def resetCopyWaveLimits(self, null=None):
        """
        Reset the spin boxes for selecting the data limits to the current wave's max values.
        """
        wave = (
            self._ui.copyWaveOriginalWave.model()
            .index(self._ui.copyWaveOriginalWave.currentIndex(), 0)
            .internalPointer()
        )
        maxIndex = wave.length()
        self._ui.copyWaveStartingIndex.setMaximum(maxIndex)
        self._ui.copyWaveEndingIndex.setMaximum(maxIndex)
        Util.setWidgetValue(self._ui.copyWaveEndingIndex, maxIndex)

    def parseFunction(self, waveLength, functionString):
        """
        Parse the function string into an actual function and return the data
        for the wave. Any python math and numpy functions are allowed.

        Special values:
        w_name - the wave with name 'name'
        s_val  - a special value, see the list below

        s_ values:
            s_index - 0-based row index
            s_oneindex - 1-based row index 
        """

        specialValuesList = Util.uniqueList(re.findall("s_\w*", functionString))
        specialValuesString = str.join(", ", specialValuesList)

        wavesList = Util.uniqueList(re.findall("w_\w*", functionString))
        wavesString = str.join(", ", wavesList)

        specialValuesAndWavesList = Util.uniqueList(re.findall("[s|w]_\w*", functionString))
        specialValuesAndWavesString = str.join(", ", specialValuesAndWavesList)

        # First, let's check if this is a simple function that can be performed completely
        # with built-ins, in which case we don't need to worry about getting any special
        # values and waves
        if len(specialValuesAndWavesList) == 0:
            return eval(functionString)

        # Need to create the lambda string first so that we can expand all
        # the arguments to the lambda before creating the actual anonymous
        # function.
        function = eval("lambda " + specialValuesAndWavesString + ": eval('" + str(functionString) + "')")

        # Determine the length of the smallest wave, so that we don't apply the function past that
        waveLengths = []
        if waveLength > 0:
            waveLengths.append(waveLength)
        for waveName in wavesList:
            waveNameNoPrefix = waveName[2:]
            waveLengths.append(len(self._app.waves().wave(waveNameNoPrefix).data()))

        if len(waveLengths) == 0:
            waveLength = 0
        else:
            waveLength = min(waveLengths)

        # Define s_ values
        s_index = range(waveLength)
        s_oneindex = range(1, waveLength + 1)

        # Define waves that are used in the function
        for waveName in wavesList:
            waveNameNoPrefix = waveName[2:]
            exec(str(waveName) + " = " + str(self._app.waves().wave(waveNameNoPrefix).data()[:waveLength]))

        # Apply the function
        data = eval("map(function, " + specialValuesAndWavesString + ")")

        return data

    def insertWaveIntoFunction(self):
        """
        Take the wave from functionInsertWave and insert it into the function definition.
        """

        waveName = (
            self._ui.functionInsertWave.model()
            .index(self._ui.functionInsertWave.currentIndex(), 0)
            .internalPointer()
            .name()
        )
        self._ui.functionEquation.insert("w_" + str(waveName))
        return True

    ####
    # Modify/Remove Wave tab
    ####
    def updateModifyWaveUi(self, *args):
        """
        Update the wave options based on the current wave.  This slot will be
        called whenever the selection has changed.
        """

        if self._ui.modifyWave_selectWave.currentIndex():
            wave = self._wavesListModel.waveByRow(self._ui.modifyWave_selectWave.currentIndex().row())

            if wave:
                Util.setWidgetValue(self._ui.modifyWave_waveName, wave.name())
                Util.setWidgetValue(self._ui.modifyWave_dataType, wave.dataType())

    def modifyWave(self):
        """
        Set the selected wave to have the currently-selected options.
        """

        currentIndex = self._ui.modifyWave_selectWave.currentIndex()
        if currentIndex:
            wave = self._wavesListModel.waveByRow(self._ui.modifyWave_selectWave.currentIndex().row())

            # Make sure the user wants to change the wave's name
            if wave.name() != Util.getWidgetValue(self._ui.modifyWave_waveName) and not self._app.waves().goodWaveName(
                Util.getWidgetValue(self._ui.modifyWave_waveName)
            ):
                warningMessage = QMessageBox()
                warningMessage.setWindowTitle("Error!")
                warningMessage.setText(
                    "You are trying to change the wave name, but the one you have chosen has already been used. Please enter a new name."
                )
                warningMessage.setIcon(QMessageBox.Critical)
                warningMessage.setStandardButtons(QMessageBox.Ok)
                warningMessage.setDefaultButton(QMessageBox.Ok)
                result = warningMessage.exec_()
                return False

            # Make sure the user wants to actually change the data type
            if wave.dataType() != Util.getWidgetValue(self._ui.modifyWave_dataType):
                warningMessage = QMessageBox()
                warningMessage.setWindowTitle("Warning!")
                warningMessage.setText(
                    "If you change the data type, then you may lose data if it cannot be properly converted."
                )
                warningMessage.setInformativeText("Are you sure you want to continue?")
                warningMessage.setIcon(QMessageBox.Warning)
                warningMessage.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
                warningMessage.setDefaultButton(QMessageBox.No)
                result = warningMessage.exec_()

                if result != QMessageBox.Yes:
                    return False

            # All warnings have been accepted, so we can continue with actually modifying the wave
            wave.setName(Util.getWidgetValue(self._ui.modifyWave_waveName))
            wave.setDataType(Util.getWidgetValue(self._ui.modifyWave_dataType))

            self._ui.modifyWave_selectWave.setCurrentIndex(currentIndex)

        return True

    def removeWave(self):
        """Remove wave from the list of all waves in the main window."""
        wavesToRemove = []

        currentIndex = self._ui.modifyWave_selectWave.currentIndex()
        row = currentIndex.row()
        if currentIndex:
            warningMessage = QMessageBox()
            warningMessage.setWindowTitle("Warning!")
            warningMessage.setText("You are about to delete a wave.")
            warningMessage.setInformativeText("Are you sure you want to continue?")
            warningMessage.setIcon(QMessageBox.Warning)
            warningMessage.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
            warningMessage.setDefaultButton(QMessageBox.No)
            result = warningMessage.exec_()

            if result != QMessageBox.Yes:
                return False

            # Determine the next row to select
            newRow = row
            if row >= self._wavesListModel.rowCount() - 1:
                newRow = row - 1

            self._app.waves().removeWave(self._wavesListModel.waveNameByRow(row))
            self._ui.modifyWave_selectWave.setCurrentIndex(self._wavesListModel.index(newRow))
            self._ui.modifyWave_selectWave.selectionModel().select(
                self._wavesListModel.index(newRow), QItemSelectionModel.ClearAndSelect
            )

    def load(self):
        self.window = SubWindow(self._app.ui.workspace)

        self.menuEntry = QAction(self._app)
        self.menuEntry.setObjectName("actionManageWavesWidget")
        self.menuEntry.setText("Manage Waves")
        self.menuEntry.triggered.connect(self.window.show)
        self.menu = vars(self._app.ui)["menuData"]
        self.menu.addAction(self.menuEntry)

        self.buildWidget()
        self.window.setWidget(self._widget)
        self._widget.setParent(self.window)

        self.window.hide()

    def unload(self):
        # Disconnect some slots
        self.menuEntry.triggered.disconnect()

        self._widget.deleteLater()
        self.window.deleteLater()
        self.menu.removeAction(self.menuEntry)

    def reload(self):
        self.setModels()
class QTextOptionsButton(QPushButton):
    """A push button that is used to select a font and text options."""

    def __init__(self, *args):
        QPushButton.__init__(self, *args)
        self._app = QApplication.instance().window

        self.textOptions = Property.TextOptions()

        # Set initial textOptions values based on application preferences
        if 'preferences' in self._app.__dict__.keys():
            preferenceTextOptions = self._app.preferences.getInternal('textOptions')
            if preferenceTextOptions is not None:
                self.setTextOptions(preferenceTextOptions)

        # Create dialog
        self._dialog = QTextOptionsWidget(self)
        self._ui = Ui_TextOptionsWidget()
        self._ui.setupUi(self._dialog)
        self._dialog.setUiObject(self._ui)

        self._window = SubWindow(self._app.ui.workspace)
        self._window.setWidget(self._dialog)
        self._dialog.setParent(self._window)
        
        self.hideTextOptionsWidget()
        
        # Create font list from matplotlib fonts
        fontPropList = fm.createFontList(fm.findSystemFonts())
        
        # Get just the names
        fontList = map(lambda x:x.name, fontPropList)
        
        # Unique and sort the names
        fontList = list(set(fontList))
        fontList.sort()
        
        # Enter fonts into the ui widget
        self._ui.name.addItems(fontList)
        
    def getOption(self, option):
        return self.textOptions.get()[option]

    def getTextOptions(self):
        return Property.TextOptions(self.textOptions)

    def setTextOptions(self, textOptions={}):
        """
        Set text options.
        """
        self.textOptions.set(textOptions)

    def showTextOptionsWidget(self):
        """
        Create a dialog box to select a font and text options.
        """
        
        self._dialog.resetUi()
        self._window.show()
    
    def hideTextOptionsWidget(self):
        self._window.hide()
Example #10
0
class ImportCSV(Module):
    """Module to import data as CSV (or TSV, etc)."""

    def __init__(self):
        Module.__init__(self)

    def buildWidget(self):
        self._widget = QWidget()
        self._ui = Ui_ImportCSV()
        self._ui.setupUi(self._widget)

        # Connect button signals
        self._ui.csvFileNameButton.clicked.connect(self.csvFileSelector)
        self._ui.loadDataButton.clicked.connect(self.loadData)
        self._ui.importDataButton.clicked.connect(self.importData)
        self._ui.data.cellChanged.connect(self.handleDataChange)

    def csvFileSelector(self):
        """Button-lineedit link"""
        directory = os.path.dirname(Util.getWidgetValue(self._ui.csvFileName))
        if not os.path.isdir(directory):
            directory = self._app.preferences.getInternal('projectDirectory')

        csvFile = str(QFileDialog.getOpenFileName(self._app.ui.workspace, "Select Data File", directory, "Comma Separated Values (*.csv);;All Files(*)"))

        if csvFile != "":
            return Util.setWidgetValue(self._ui.csvFileName, csvFile)
        return False

    def loadData(self):
        """Load data into the widget for viewing before importing into the application."""
        
        # Block the cellChanged signal, because all the cells are going to be changed and we
        # are dealing with that in this method.
        self._ui.data.blockSignals(True)

        dataTable = self._ui.data

        self.clearTable()

        # Get data from file
        csvFile = Util.getWidgetValue(self._ui.csvFileName)
        if os.path.isfile(csvFile):
            
            rows = []
            delimiterText = Util.getWidgetValue(self._ui.delimiterButtonGroup)
            if delimiterText == "Comma":
                rows = csv.reader(open(csvFile), dialect="excel", delimiter=",")
            elif delimiterText == "Tab":
                rows = csv.reader(open(csvFile), dialect="excel-tab")
            elif delimiterText == "Other":
                rows = csv.reader(open(csvFile), dialect="excel", delimiter=Util.getWidgetValue(self._ui.otherDelimiter))
            else:
                rows = csv.reader(open(csvFile), dialect="excel", delimiter=",")

            for rownum, row in enumerate(rows):
                # Make sure the cells exist to enter the data into
                dataTable.insertRow(rownum)
                if len(row) > dataTable.columnCount():
                    for i in range(dataTable.columnCount(), len(row)):
                        dataTable.insertColumn(dataTable.columnCount())
                # Add this row to the table
                for colnum, item in enumerate(row):
                    dataTable.setItem(rownum, colnum, QTableWidgetItem(item))
        
        # Hide the QT default column header, since we cannot edit it easily
        dataTable.horizontalHeader().hide()

        # Add a row for setting the data type of each wave
        dataTable.insertRow(0)
        defaultType = Util.getWidgetValue(self._ui.defaultDataType)
        for col in range(dataTable.columnCount()):
            typeBox = QComboBox()
            typeBox.addItem("Integer")
            typeBox.addItem("Decimal")
            typeBox.addItem("String")
            Util.setWidgetValue(typeBox, defaultType)
            dataTable.setCellWidget(0, col, typeBox)

        # Potential wave names
        waveNamePrefix = Util.getWidgetValue(self._ui.waveNamePrefix)
        tempWaveNames = []
        if Util.getWidgetValue(self._ui.useWaveNamePrefix):
            tempWaveNames = self._app.waves().findGoodWaveNames(dataTable.columnCount(), waveNamePrefix)
        else:
            tempWaveNames = self._app.waves().findGoodWaveNames(dataTable.columnCount())

        # Wave names can either be generated or taken from the first row in the file
        if not Util.getWidgetValue(self._ui.firstRowWaveNames):
            # Generate headers
            dataTable.insertRow(1)
            
            # PyQt does not have QList support yet, so this is a hack to get around that
            for col,name in enumerate(tempWaveNames):
                dataTable.setItem(1, col, QTableWidgetItem(name))
        else:
            # Use the first row of data, but check to see if there is text for each column
            # and if there is no text for a column, add in a tempWaveName entry
            for col in range(dataTable.columnCount()):
                if not dataTable.item(1, col) or str(dataTable.item(1, col).text()).strip() == "":
                    dataTable.setItem(1, col, QTableWidgetItem(tempWaveNames.pop(0)))
                else:
                    # For the names that came from the file, add the prefix specified by the user
                    if Util.getWidgetValue(self._ui.useWaveNamePrefix):
                        dataTable.item(1, col).setText(str(waveNamePrefix) + dataTable.item(1, col).text())


        # Edit the name so that it could be valid (no spaces, etc). But it might
        # still be a duplicate.
        self.validateWaveNames()

        # Adjust the row headers so that they number correctly with the wave names in the first row
        rowLabels = QStringList("Type")
        rowLabels.append("Name")
        for row in range(1, dataTable.rowCount()):
            rowLabels.append(str(row))
        dataTable.setVerticalHeaderLabels(rowLabels)

        # Verify that all wave names are acceptable for the app's waves object
        self.verifyGoodWaveNames()

        self._ui.data.blockSignals(False)

        # Resize rows and columns
        dataTable.resizeRowsToContents()
        dataTable.resizeColumnsToContents()

    def clearTable(self):
        dataTable = self._ui.data

        # Reset table to be empty
        for i in range(dataTable.rowCount()):
            dataTable.removeRow(0)
        for i in range(dataTable.columnCount()):
            dataTable.removeColumn(0)

    def validateWaveNames(self):
        """
        Run Wave.validateWaveName on all potential wave names.
        """

        dataTable = self._ui.data

        for col in range(dataTable.columnCount()):
            if dataTable.item(1, col):
                dataTable.setItem(1, col, QTableWidgetItem(Wave.validateWaveName(str(dataTable.item(1, col).text()))))

    def verifyGoodWaveNames(self):
        """
        Verify that all the wave names provided will be acceptable in the application's waves object.
        If there are any conflicts, then disable importing and mark the offending names.
        """
        
        dataTable = self._ui.data
        
        allNamesAreGood = True
        importWaveNames = []

        for col in range(dataTable.columnCount()):
            if dataTable.item(1, col):
                name = str(dataTable.item(1, col).text())
                if name in importWaveNames or not self._app.waves().goodWaveName(name):
                    # Bad name, highlight
                    dataTable.item(1, col).setBackground(QBrush(QColor('red')))
                    allNamesAreGood = False
                else:
                    # Good name
                    dataTable.item(1, col).setBackground(QBrush(QColor('lightgreen')))
                importWaveNames.append(name)

        # Disable the import button
        if allNamesAreGood:
            self._ui.importDataButton.setEnabled(True)
        else:
            self._ui.importDataButton.setEnabled(False)

        return allNamesAreGood

    def handleDataChange(self, row, column):
        """
        If data in a cell is changed, then this method will be called.

        Currently, if a name cell is edited, then we re-verify the wave names.
        """

        if row == 1:
            self.verifyGoodWaveNames()

    def importData(self):
        """Import data into the application as waves."""
        
        dataTable = self._ui.data
        
        # Loop through all waves
        for col in range(dataTable.columnCount()):
            dataType = Util.getWidgetValue(dataTable.cellWidget(0, col))
            wave = Wave(str(dataTable.item(1, col).text()), dataType)
            for row in range(2, dataTable.rowCount()):
                if dataTable.item(row, col):
                    wave.push(str(dataTable.item(row, col).text()))
                else:
                    wave.push('')
            self._app.waves().addWave(wave)

        # Close window
        self.clearTable()
        self.window.hide()


    def load(self):
        self.window = SubWindow(self._app.ui.workspace)

        self.menuEntry = QAction(self._app)
        self.menuEntry.setObjectName("actionImportCSV")
        self.menuEntry.setShortcut("Ctrl+I")
        self.menuEntry.setText("Import CSV Data")
        self.menuEntry.triggered.connect(self.window.show)
        
        # Check if menu already exists
        if "menuImport" not in vars(self._app.ui).keys():
            self._app.ui.menuImport = QMenu(self._app.ui.menuFile)
            self._app.ui.menuImport.setObjectName("menuImport")
            self._app.ui.menuImport.setTitle(QApplication.translate("MainWindow", "Import", None, QApplication.UnicodeUTF8))
            self._app.ui.menuFile.addAction(self._app.ui.menuImport.menuAction())
        
        self.menu = vars(self._app.ui)["menuImport"]
        self.menu.addAction(self.menuEntry)

        self.buildWidget()
        self.window.setWidget(self._widget)
        self._widget.setParent(self.window)

        self.window.hide()

    def unload(self):

        self._widget.deleteLater()
        self.window.deleteLater()
        self.menu.removeAction(self.menuEntry)

    def setDefaults(self):
        for child in self._widget.children():
            print child
            del child
        self._ui.setupUi(self._widget)
Example #11
0
class ExportData(Module):
    """Module to export data in any format."""

    def __init__(self):
        Module.__init__(self)

    def buildWidget(self):
        self._widget = QWidget()
        self._ui = Ui_ExportData()
        self._ui.setupUi(self._widget)

        self.setModels()

        # Connect button signals
        self._ui.fileNameButton.clicked.connect(self.fileSelector)
        self._ui.addWaveButton.clicked.connect(self.addWave)
        self._ui.removeWaveButton.clicked.connect(self.removeWave)
        self._ui.exportDataButton.clicked.connect(self.exportData)

    def setModels(self):
        # Set up model and view
        self._allWavesListModel = self._app.model('appWaves')
        self._ui.allWavesListView.setModel(self._allWavesListModel)
        self._fileWavesListModel = WavesListModel([])
        self._ui.fileWavesListView.setModel(self._fileWavesListModel)

    def fileSelector(self):
        """Button-lineedit link"""
        directory = os.path.dirname(Util.getWidgetValue(self._ui.fileName))
        if not os.path.isdir(directory):
            directory = self._app.preferences.getInternal('projectDirectory')

        fileName = str(QFileDialog.getOpenFileName(self._app.ui.workspace, "Select Data File", directory, "All Files(*)"))

        if fileName != "":
            return Util.setWidgetValue(self._ui.fileName, fileName)
        return False

    def addWave(self):
        selectedIndexes = self._ui.allWavesListView.selectedIndexes()
        for index in selectedIndexes:
            self._fileWavesListModel.appendRow(self._allWavesListModel.waveNameByRow(index.row()))

    def removeWave(self):
        selectedIndexes = self._ui.fileWavesListView.selectedIndexes()
        selectedRows = map(lambda x:x.row(), selectedIndexes)
        selectedRows.sort()
        selectedRows.reverse()
        for row in selectedRows:
            self._fileWavesListModel.removeRow(row)

    def exportData(self):
        fileName = Util.getWidgetValue(self._ui.fileName)

        if os.path.exists(fileName):
            if os.path.isfile(fileName):
                # Ask about overwriting
                warningMessage = QMessageBox()
                warningMessage.setWindowTitle("Warning!")
                warningMessage.setText("The filename you have chosen - " + str(fileName) + " - already exists.")
                warningMessage.setInformativeText("Do you want to overwrite the file?")
                warningMessage.setIcon(QMessageBox.Warning)
                warningMessage.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
                warningMessage.setDefaultButton(QMessageBox.No)
                result = warningMessage.exec_()

                if result != QMessageBox.Yes:
                    return False
            else:
                # Do not try to overwrite a directory or link
                return False
        
        # Get waves
        waveNames = self._ui.fileWavesListView.model().orderedWaveNames()
        
        with open(fileName, 'w') as fileHandle:
            if Util.getWidgetValue(self._ui.outputType) == 'Delimited':
                delimiterText = Util.getWidgetValue(self._ui.delimiterButtonGroup)
                if delimiterText == 'Comma':
                    fileWriter = csv.writer(fileHandle, dialect="excel", delimiter=",")
                elif delimiterText == 'Tab':
                    fileWriter = csv.writer(fileHandle, dialect="excel-tab")
                elif delimiterText == 'Other':
                    fileWriter = csv.writer(fileHandle, dialect="excel", delimiter=Util.getWidgetValue(self._ui.delimitedOtherDelimiter))
                else:
                    fileWriter = csv.writer(fileHandle, dialect="excel", delimiter=",")
    
                dataDirection = Util.getWidgetValue(self._ui.dataDirectionButtonGroup)
                
                rows = []
                for waveName in waveNames:
                    wave = self._app.waves().wave(waveName)
                    row = wave.data()
                    row.insert(0, wave.name())
                    rows.append(row)

                if dataDirection == "Rows":
                    fileWriter.writerows(rows)
                elif dataDirection == "Columns":
                    # Transpose the rows into columns
                    columns = map(lambda *row: ['' if elem is None else elem for elem in row], *rows)
                    fileWriter.writerows(columns)

    def load(self):
        self.window = SubWindow(self._app.ui.workspace)

        self.menuEntry = QAction(self._app)
        self.menuEntry.setObjectName("actionExportData")
        self.menuEntry.setShortcut("Ctrl+E")
        self.menuEntry.setText("Export Data")
        self.menuEntry.triggered.connect(self.window.show)

        # Check if menu already exists
        if "menuExport" not in vars(self._app.ui).keys():
            self._app.ui.menuExport = QMenu(self._app.ui.menuFile)
            self._app.ui.menuExport.setObjectName("menuExport")
            self._app.ui.menuExport.setTitle(QApplication.translate("MainWindow", "Export", None, QApplication.UnicodeUTF8))
            self._app.ui.menuFile.addAction(self._app.ui.menuExport.menuAction())
        
        self.menu = vars(self._app.ui)["menuExport"]
        self.menu.addAction(self.menuEntry)
        
        self.buildWidget()
        self.window.setWidget(self._widget)
        self._widget.setParent(self.window)

        self.window.hide()

    def unload(self):

        self._widget.deleteLater()
        self.window.deleteLater()
        self.menu.removeAction(self.menuEntry)

    def reload(self):
        self.setModels()
Example #12
0
class CreateTableDialog(Module):
    """Module to display the Create Table dialog window."""

    def __init__(self):
        Module.__init__(self)

    def buildWidget(self):
        """Create the widget and populate it."""

        # Create enclosing widget and UI
        self._widget = QWidget()
        self._ui = Ui_CreateTableDialog()
        self._ui.setupUi(self._widget)
        
        self.setModels()

        # Connect some slots
        self._ui.createTableButton.clicked.connect(self.createTable)
        self._ui.closeWindowButton.clicked.connect(self.closeWindow)
        self._ui.addWaveButton.clicked.connect(self.addWaveToTable)
        self._ui.removeWaveButton.clicked.connect(self.removeWaveFromTable)

    def setModels(self):
        # Set up model and view
        self._allWavesListModel = self._app.model('appWaves')
        self._ui.allWavesListView.setModel(self._allWavesListModel)
        self._tableWavesListModel = WavesListModel([])
        self._ui.tableWavesListView.setModel(self._tableWavesListModel)

    def closeWindow(self):
        self.resetForm()
        self._widget.parent().close()


    def addWaveToTable(self):
        selectedIndexes = self._ui.allWavesListView.selectedIndexes()
        for index in selectedIndexes:
            self._tableWavesListModel.appendRow(self._allWavesListModel.waveNameByRow(index.row()))

    def removeWaveFromTable(self):
        selectedIndexes = self._ui.tableWavesListView.selectedIndexes()
        selectedRows = map(lambda x:x.row(), selectedIndexes)
        selectedRows.sort()
        selectedRows.reverse()
        for row in selectedRows:
            self._tableWavesListModel.removeRow(row)

    def createTable(self):
        """
        Create the table.
        """

        tableName = Util.getWidgetValue(self._ui.tableName)
        waves = self._tableWavesListModel.waves()
        if len(waves) == 0:
            warningMessage = QMessageBox()
            warningMessage.setWindowTitle("Problem!")
            warningMessage.setText("You must select at least one wave in order to create a table.")
            warningMessage.setIcon(QMessageBox.Critical)
            warningMessage.setStandardButtons(QMessageBox.Ok)
            warningMessage.setDefaultButton(QMessageBox.Ok)
            result = warningMessage.exec_()
            return False

        names = map(Wave.getName, waves)
        self._app.createTable(waves, tableName)
        self.closeWindow()
        return True

    def resetForm(self):
        Util.setWidgetValue(self._ui.tableName, "Table")
        self._allWavesListModel.doReset()
        self._tableWavesListModel.removeAllWaves()
        self._tableWavesListModel.doReset()

    def load(self):
        self.window = SubWindow(self._app.ui.workspace)

        self.menuEntry = QAction(self._app)
        self.menuEntry.setObjectName("actionNewTableDialog")
        self.menuEntry.setShortcut("Ctrl+T")
        self.menuEntry.setText("New Table")
        self.menuEntry.triggered.connect(self.window.show)
        self.menu = vars(self._app.ui)["menuNew"]
        self.menu.addAction(self.menuEntry)

        self.buildWidget()
        self.window.setWidget(self._widget)
        self._widget.setParent(self.window)

        self.window.hide()

    def unload(self):
        # Disconnect some slots
        self.menuEntry.triggered.disconnect()

        self._widget.deleteLater()
        self.window.deleteLater()
        self.menu.removeAction(self.menuEntry)

    def reload(self):
        self.setModels()
        self.resetForm()
Example #13
0
class ImportBinary(Module):
    """Module to import data from a binary file."""

    dataTypes = {   'Signed Integer (1)':     { 'dtype': 'Integer', 'char': 'b', 'numbytes': 1 },
                    'Unsigned Integer (1)':   { 'dtype': 'Integer', 'char': 'B', 'numbytes': 1 },
                    'Short (2)':              { 'dtype': 'Integer', 'char': 'h', 'numbytes': 2 },
                    'Unsigned Short (2)':     { 'dtype': 'Integer', 'char': 'H', 'numbytes': 2 },
                    'Integer (4)':            { 'dtype': 'Integer', 'char': 'i', 'numbytes': 4 },
                    'Unsigned Integer (4)':   { 'dtype': 'Integer', 'char': 'I', 'numbytes': 4 },
                    'Long (8)':               { 'dtype': 'Integer', 'char': 'l', 'numbytes': 8 },
                    'Unsigned Long (8)':      { 'dtype': 'Integer', 'char': 'L', 'numbytes': 8 },
                    'Float (4)':              { 'dtype': 'Decimal', 'char': 'f', 'numbytes': 4 },
                    'Double (8)':             { 'dtype': 'Decimal', 'char': 'd', 'numbytes': 8 },
                }

    byteOrders = {  'Native': '@',
                    'Big Endian': '>',
                    'Little Endian': '<',
                    'Network': '!',
                 }

    def __init__(self):
        Module.__init__(self)

    def buildWidget(self):
        self._widget = QWidget()
        self._ui = Ui_ImportBinary()
        self._ui.setupUi(self._widget)

        # Connect button signals
        self._ui.fileNameButton.clicked.connect(self.fileSelector)
        self._ui.importDataButton.clicked.connect(self.importData)

    def fileSelector(self):
        """Button-lineedit link"""
        directory = os.path.dirname(Util.getWidgetValue(self._ui.fileName))
        if not os.path.isdir(directory):
            directory = self._app.preferences.getInternal('projectDirectory')

        fileName = str(QFileDialog.getOpenFileName(self._app.ui.workspace, "Select Data File", directory, "Binary (*.bin *.dat);;All Files(*)"))

        if fileName != "":
            return Util.setWidgetValue(self._ui.fileName, fileName)
        return False


    def importData(self):
        """Import data into the application as waves."""
        
        # Check if the proposed wave name is acceptable
        validatedWaveName = Wave.validateWaveName(Util.getWidgetValue(self._ui.waveName))
        if not self._app.waves().goodWaveName(validatedWaveName):
            badWaveNameMessage = QMessageBox()
            badWaveNameMessage.setText("Wave name is not allowed or already in use.  Please change the wave name.")
            badWaveNameMessage.exec_()
            return False

        # Get data type and size
        uiDataType = Util.getWidgetValue(self._ui.dataType)
        uiByteOrder = Util.getWidgetValue(self._ui.byteOrder)
        dataType = self.dataTypes[uiDataType]['dtype']
        dataTypeChar = self.dataTypes[uiDataType]['char']
        numBytes = self.dataTypes[uiDataType]['numbytes']
        byteOrder = self.byteOrders[uiByteOrder]

        # Load data
        fileName = Util.getWidgetValue(self._ui.fileName)
        if os.path.isfile(fileName):
            data = []
            wave = Wave(str(validatedWaveName), dataType)
            self._app.waves().addWave(wave)

            fh = open(fileName, 'rb')
            binDatum = fh.read(numBytes)
            while binDatum != "":
                try:
                    datum = struct.unpack(byteOrder + dataTypeChar, binDatum)
                    wave.push(datum[0])
                except:
                    pass
                binDatum = fh.read(numBytes)

            




#            data = array.array(dataTypeChar)
#            numDataPoints = os.path.getsize(fileName) / data.itemsize
#            fh = open(fileName, 'rb')
#            data.fromfile(fh, numDataPoints)
#
#            wave = Wave(str(validatedWaveName), dataType)
#            wave.replaceData(data)
#            self._app.waves().addWave(wave)

        # Close window
        self.window.hide()







    def load(self):
        self.window = SubWindow(self._app.ui.workspace)

        self.menuEntry = QAction(self._app)
        self.menuEntry.setObjectName("actionImportBinary")
        self.menuEntry.setShortcut("Ctrl+B")
        self.menuEntry.setText("Import Binary Data")
        self.menuEntry.triggered.connect(self.window.show)
        
        # Check if menu already exists
        if "menuImport" not in vars(self._app.ui).keys():
            self._app.ui.menuImport = QMenu(self._app.ui.menuFile)
            self._app.ui.menuImport.setObjectName("menuImport")
            self._app.ui.menuImport.setTitle(QApplication.translate("MainWindow", "Import", None, QApplication.UnicodeUTF8))
            self._app.ui.menuFile.addAction(self._app.ui.menuImport.menuAction())
        
        self.menu = vars(self._app.ui)["menuImport"]
        self.menu.addAction(self.menuEntry)

        self.buildWidget()
        self.window.setWidget(self._widget)
        self._widget.setParent(self.window)

        self.window.hide()

    def unload(self):

        self._widget.deleteLater()
        self.window.deleteLater()
        self.menu.removeAction(self.menuEntry)