示例#1
0
    def createWidgets(self):
        """Create the main widget and all children."""

        mainWidget = QtGui.QWidget(self)
        
        # Left-side is sidebar
        self.sidebarLayout = QtGui.QVBoxLayout()
        self.fileWidget = self.createFileWidget()
        self.attackWidget = self.createAttackWidget()
        self.attackWidget.setEnabled(False)
        self.algoWidget = self.createAlgoWidget()
        self.algoWidget.setEnabled(False)
        self.sidebarLayout.addWidget(self.fileWidget)
        self.sidebarLayout.addWidget(self.attackWidget)
        self.sidebarLayout.addWidget(self.algoWidget)
        self.sidebarLayout.addWidget(QtGui.QLabel(""), stretch=1)

        # Right-side is graph and toolbar
        #self.resultsGraph = ResultsGraph(parent=mainWidget)
        #self.resultsGraph.hide()
        self.graphWidget = QtGui.QWidget(mainWidget)
        self.powerGraph = PowerGraph(self.graphWidget)
        self.toolbar = NavigationToolbar(self.powerGraph, self.graphWidget)
        self.graphLayout = QtGui.QVBoxLayout()
        self.graphLayout.addWidget(self.powerGraph)
        self.graphLayout.addWidget(self.toolbar)

        # Add children to layout and set focus to main widget
        mainLayout = QtGui.QHBoxLayout()
        mainLayout.addLayout(self.sidebarLayout)
        mainLayout.addLayout(self.graphLayout, stretch=1)
        mainWidget.setLayout(mainLayout)
        return mainWidget
示例#2
0
class MainWindow(QtGui.QMainWindow):

    """The main window of the application."""

    def __init__(self):
        """Initialize the main window and create all child widgets."""

        super(MainWindow, self).__init__()
        self.setGeometry(50, 50, 1200, 600)
        self.setWindowTitle('CSV Data Analysis')
        self.setWindowIcon(QtGui.QIcon(ICON_FILE))
        self.setAttribute(QtCore.Qt.WA_DeleteOnClose)

        # Widget creation
        self.statusBar()
        self.mainWidget = self.createWidgets()
        self.mainWidget.setFocus()
        self.setCentralWidget(self.mainWidget)
        self.show()


    #==================== WIDGETS ====================#

    def createWidgets(self):
        """Create the main widget and all children."""

        mainWidget = QtGui.QWidget(self)
        
        # Left-side is sidebar
        self.sidebarLayout = QtGui.QVBoxLayout()
        self.fileWidget = self.createFileWidget()
        self.attackWidget = self.createAttackWidget()
        self.attackWidget.setEnabled(False)
        self.algoWidget = self.createAlgoWidget()
        self.algoWidget.setEnabled(False)
        self.sidebarLayout.addWidget(self.fileWidget)
        self.sidebarLayout.addWidget(self.attackWidget)
        self.sidebarLayout.addWidget(self.algoWidget)
        self.sidebarLayout.addWidget(QtGui.QLabel(""), stretch=1)

        # Right-side is graph and toolbar
        #self.resultsGraph = ResultsGraph(parent=mainWidget)
        #self.resultsGraph.hide()
        self.graphWidget = QtGui.QWidget(mainWidget)
        self.powerGraph = PowerGraph(self.graphWidget)
        self.toolbar = NavigationToolbar(self.powerGraph, self.graphWidget)
        self.graphLayout = QtGui.QVBoxLayout()
        self.graphLayout.addWidget(self.powerGraph)
        self.graphLayout.addWidget(self.toolbar)

        # Add children to layout and set focus to main widget
        mainLayout = QtGui.QHBoxLayout()
        mainLayout.addLayout(self.sidebarLayout)
        mainLayout.addLayout(self.graphLayout, stretch=1)
        mainWidget.setLayout(mainLayout)
        return mainWidget

    def createFileWidget(self):
        """Create the file browsing section"""

        mainWidget = QtGui.QWidget(self)
        title = QtGui.QLabel("Choose the input file: ")
        title.setFont(QtGui.QFont("Arial", 10, QtGui.QFont.Bold))

        self.inputEdit = QtGui.QLineEdit()
        self.inputEdit.setEnabled(False)
        self.attackEdit = QtGui.QLineEdit()
        self.resultsEdit = QtGui.QLineEdit()
        
        spacer = QtGui.QLabel("", )
        browse = QtGui.QPushButton('Browse...', )
        browse.clicked.connect(self.browseFile)
        save = QtGui.QPushButton('Save Attacks', )
        save.clicked.connect(self.saveAttackFile)
        
        layout = QtGui.QGridLayout()
        layout.addWidget(title, 0, 0, 1, -1)
        layout.addWidget(self.inputEdit, 1, 0)
        layout.addWidget(self.attackEdit, 2, 0)
        layout.addWidget(self.resultsEdit, 3, 0)
        layout.addWidget(browse, 1, 1)
        layout.addWidget(save, 2, 1)
        
        mainWidget.setLayout(layout)
        return mainWidget

    def createAttackWidget(self):
        """Create the attack section."""

        # TODO: Keep track of attacks so they can be removed later
        self.attackList = []

        mainWidget = QtGui.QWidget(self)
        title = QtGui.QLabel("Inject Attacks: ", mainWidget)
        title.setFont(QtGui.QFont("Arial", 10, QtGui.QFont.Bold))
        attackButton = QtGui.QPushButton("Add Attack...", mainWidget)
        attackButton.clicked.connect(self.addAttack)
        clearButton = QtGui.QPushButton("Clear All", mainWidget)
        clearButton.clicked.connect(self.clearAttacks)
        buttonLayout = QtGui.QHBoxLayout()
        buttonLayout.addWidget(attackButton)
        buttonLayout.addWidget(clearButton)
        
        self.attackLayout = QtGui.QVBoxLayout()
        self.attackLayout.addWidget(title)
        self.attackLayout.addLayout(buttonLayout)
        mainWidget.setLayout(self.attackLayout)
        return mainWidget

    def createAlgoWidget(self):
        """Create the analysis section."""

        mainWidget = QtGui.QWidget(self)
        title = QtGui.QLabel("Run Analysis: ", mainWidget)
        title.setFont(QtGui.QFont("Arial", 10, QtGui.QFont.Bold))

        #TODO: input validation
        self.emaSpin = QtGui.QDoubleSpinBox(mainWidget)
        self.emaSpin.setDecimals(3)
        self.emaSpin.setRange(0.001, 1)
        self.emaSpin.setSingleStep(0.01)
        self.emaSpin.setValue(1.0)

        self.severityButton1 = QtGui.QRadioButton("w=1.00, L=3.719", mainWidget)     
        self.severityButton2 = QtGui.QRadioButton("w=0.84, L=3.719", mainWidget)
        self.severityButton3 = QtGui.QRadioButton("w=0.53, L=3.714", mainWidget)
        self.severityButtonOther = QtGui.QRadioButton("Other", mainWidget)
        buttonLayout = QtGui.QVBoxLayout()
        buttonLayout.addWidget(self.severityButton1)
        buttonLayout.addWidget(self.severityButton2)
        buttonLayout.addWidget(self.severityButton3)
        buttonLayout.addWidget(self.severityButtonOther)
        buttonBox = QtGui.QWidget()
        buttonBox.setLayout(buttonLayout)
        
        self.severitySpinW = QtGui.QDoubleSpinBox(mainWidget)
        self.severitySpinL = QtGui.QDoubleSpinBox(mainWidget)
        self.severitySpinW.setDecimals(3)
        self.severitySpinL.setDecimals(3)
        self.severitySpinW.setRange(0, 1)
        self.severitySpinL.setRange(0, 10)
        self.severitySpinW.setSingleStep(0.01)
        self.severitySpinL.setSingleStep(0.01)
        self.severitySpinW.setEnabled(False)
        self.severitySpinL.setEnabled(False)
        
        def toggleSpinBoxes():
            if self.severityButtonOther.isChecked():
                self.severitySpinW.setEnabled(True)
                self.severitySpinL.setEnabled(True)
            else:
                self.severitySpinW.setEnabled(False)
                self.severitySpinL.setEnabled(False)
                if self.severityButton1.isChecked():
                    self.severitySpinW.setValue(1)
                    self.severitySpinL.setValue(3.719)
                elif self.severityButton2.isChecked():
                    self.severitySpinW.setValue(.84)
                    self.severitySpinL.setValue(3.719)
                elif self.severityButton3.isChecked():
                    self.severitySpinW.setValue(.53)
                    self.severitySpinL.setValue(3.714)

        self.severityButton1.toggled.connect(toggleSpinBoxes)
        self.severityButton2.toggled.connect(toggleSpinBoxes)
        self.severityButton3.toggled.connect(toggleSpinBoxes)
        self.severityButtonOther.toggled.connect(toggleSpinBoxes)
        self.severityButton1.toggle() #Default
        
        spinLayout = QtGui.QVBoxLayout()
        spinLayout.addWidget(QtGui.QLabel("w:    "))
        spinLayout.addWidget(self.severitySpinW)
        spinLayout.addWidget(QtGui.QLabel("L:    "))
        spinLayout.addWidget(self.severitySpinL)
        
        startButton = QtGui.QPushButton("Start Analysis", mainWidget)
        startButton.clicked.connect(self.startAnalysis)

        layout = QtGui.QFormLayout()
        layout.addRow(title)
        layout.addRow("EMA level (aka alpha): ", self.emaSpin)
        layout.addRow(QtGui.QLabel("Severity sensitivity parameters: "))
        layout.addRow(buttonBox, spinLayout)
        layout.addRow(startButton)
        mainWidget.setLayout(layout)
        return mainWidget
        

    #==================== HELPER FUNCTIONS ====================#

    def browseFile(self):
        """Choose a file using the file search dialog."""

        filename = str(QtGui.QFileDialog.getOpenFileName())
        if (filename != ''):
            self.checkInputFilename(filename) #Raises exception if not CSV file
            self.inputEdit.setText(filename)
            filename = filename.rstrip('.csv')
            self.attackEdit.setText(filename + '_attacked.csv')
            self.resultsEdit.setText(filename + '_results.csv')

            self.loadInputFile() #Load times and power into self.time, self.target
            self.powerGraph.graphData(self.time, self.target)
            self.statusBar().showMessage("Graphing complete.", 5000)
            self.attackWidget.setEnabled(True)
            self.algoWidget.setEnabled(True)

    def checkInputFilename(self, filename):
        """Return if the given filename is valid, raise exception otherwise"""

        if filename == '':
            self.warningDialog("No file specified.")
            raise ValueError("No file specified.")

        elif filename[-4:] != '.csv':
            self.warningDialog("File must have '.csv' extension.")
            raise ValueError("File must have '.csv' extension.")

    def loadInputFile(self):
        """Load power data from the input file."""

        csvfile = str(self.inputEdit.text())
        with open(csvfile, 'rb') as infile:
            reader = csv.reader(infile)
            reader.next()
            columns = zip(*reader)
        self.time = [dt.datetime.fromtimestamp(float(t)) for t in columns[0]]
        self.target = [float(p) for p in columns[-1]]
        self.newTarget = self.target[:]
        '''
        # Convert times from string or timestamp to datetime
        try:
            self.time = [dt.datetime.fromtimestamp(float(t)) for t in columns[0]]
        except ValueError:
            self.time = [dt.datetime.strptime(t, DATE_FORMAT) for t in columns[0]]
        '''

    def addAttack(self):
        """Open the attack dialog and get the attack parameters."""

        dialog = AttackDialog(self)
        if dialog.exec_():
            startdate, duration, intensity = dialog.get_info()
        else:
            return

        enddate = startdate + dt.timedelta(minutes=duration)
        if enddate < self.time[0] or startdate > self.time[-1]:
            self.warningDialog("Attack out of range.")
            return
        
        # TODO: Catch potential StopIteration errors
        start_id = next(id for id, val in enumerate(self.time) if val > startdate)
        end_id = next(id for id, val in enumerate(self.time) if val > enddate)
        while start_id < end_id:
            self.newTarget[start_id] += intensity
            start_id += 1
            
        class Attack(QtGui.QLabel):
            def __init__(self, parent, start, duration, intensity):
                super(Attack, self).__init__(parent)
                self.start = start
                self.end = start + dt.timedelta(minutes=duration)
                self.setText("At time %s, %.2f Watts for %d minutes"
                          % (start, intensity, duration))

        newAttack = Attack(self, startdate, duration, intensity)
        self.attackList.append(newAttack)
        self.attackLayout.addWidget(newAttack)

        self.powerGraph.graphData(self.time, self.newTarget)
        self.powerGraph.colorSpan(startdate, duration, 'green')
        self.statusBar().showMessage("Graphing complete.", 5000)
        self.algoWidget.setEnabled(False)
        #print time.mktime(startdate.timetuple())
        #print time.mktime(enddate.timetuple())
        
    def clearAttacks(self):
        """Open the attack dialog and get the attack parameters."""
        
        for attack in self.attackList:
            attack.deleteLater()
        self.attackList = []
        self.newTarget = self.target[:]
        self.powerGraph.clear()
        self.powerGraph.graphData(self.time, self.newTarget)
        self.algoWidget.setEnabled(True)

    def saveAttackFile(self):
        """Save the new data in the file given by attackFile."""

        inputFile = str(self.inputEdit.text())
        attackFile = str(self.attackEdit.text())
        self.checkInputFilename(attackFile)
        with open(inputFile, 'rb') as infile, open(attackFile, 'wb') as outfile:
            reader = csv.reader(infile)
            writer = csv.writer(outfile)
            writer.writerow(reader.next()) #Copy header row
            count = 0
            for line in reader:
                line[-1] = self.newTarget[count]
                writer.writerow(line)
                count += 1
        self.statusBar().showMessage("File %s saved" % attackFile, 5000)
        self.algoWidget.setEnabled(True)
                
    def warningDialog(self, message="Unknown error occurred."):
        warningMessage = QtGui.QMessageBox()
        warningMessage.setWindowTitle("Warning")
        warningMessage.setText(message)
        warningMessage.setIcon(QtGui.QMessageBox.Warning)
        warningMessage.exec_()

                
    #==================== ALGORITHM ====================#
    def startAnalysis(self):

        # Use filename from attackEdit instead of inputEdit if possible
        if len(self.attackList) > 0: 
            infile = str(self.attackEdit.text())
        else:
            infile = str(self.inputEdit.text())
        outfile = str(self.resultsEdit.text())
        granularity = 1
        trainingWin = 24
        forecastingInterval = 1

        print ("\nStarting analysis on %s with settings %d %d %d..." 
               % (infile, granularity, trainingWin, forecastingInterval))
               
        # Get list of features (first columns is time)
        infile = open(infile, 'rb')
        reader = csv.reader(infile)
        columns = reader.next()[1:]
        
        print "The following features were found:", columns
                
        # Algorithm settings
        algo = Algo(granularity, trainingWin, forecastingInterval, len(columns)-1)
        algo.setEMAParameter(alpha=self.emaSpin.value())
        algo.setSeverityParameters(w=self.severitySpinW.value(),
                                   L=self.severitySpinL.value())
        
        y_time = ['Timestamp']
        y_target = ['Target']
        y_predict = ['Prediction']
        anomalies = ['Anomaly']

        detected = set()
        ground_truth = set()
        
        first = True
        
        print "Beginning analysis..."
        loadingWin = LoadingWindow()
        self.mainWidget.setEnabled(False)
        count = 0
        for line in reader:

            # Read new data from file
            cur_time = float(line[0])
            new_data = np.asarray(line[1:], np.float)
            target, prediction = algo.run(new_data) # Magic!
            
            if prediction != None:
                y_time.append(cur_time)
                y_target.append(target)
                y_predict.append(float(prediction))
                
                if algo.checkSeverity(target, float(prediction)):
                    detected.add(cur_time)
                    
                    anomalies.append(1)
                else:
                    anomalies.append(0)

            cur_datetime = dt.datetime.fromtimestamp(cur_time)
            for attack in self.attackList:
                if(cur_datetime >= attack.start and cur_datetime < attack.end):
                    ground_truth.add(cur_time)
                    break
                    
            if (count % 60) == 0:
                #print "Trying time: ", cur_time
                QtGui.QApplication.processEvents()
            count += 1
            
             
        # Close the input file and save results
        infile.close()
        writeResults(outfile, (y_time, y_target, y_predict, anomalies))
        f1_scores(detected, ground_truth)
        print_stats(y_target[1:], y_predict[1:]) #Remove header
        print "Ending analysis. See %s for results." % outfile

        self.mainWidget.setEnabled(True)
        loadingWin.close()