Esempio n. 1
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)
Esempio n. 2
0
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()
Esempio n. 3
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)
Esempio n. 4
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()
Esempio n. 5
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()
Esempio n. 6
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)