class SimulationGui(QtGui.QMainWindow): """ class for the graphical user interface """ # TODO enable closing plot docks by right-clicking their name # TODO add ability to stop an simulation # TODO add ability to stop regime execution runSimulation = pyqtSignal() playbackTimeChanged = pyqtSignal() regimeFinished = pyqtSignal() finishedRegimeBatch = pyqtSignal() def __init__(self): # constructor of the base class QtGui.QMainWindow.__init__(self) self._logger = logging.getLogger(self.__class__.__name__) # Create Simulation Backend self.guiProgress = None self.cmdProgress = None self.sim = SimulatorInteractor(self) self.runSimulation.connect(self.sim.run_simulation) self.sim.simulation_finished.connect(self.simulation_finished) self.sim.simulation_failed.connect(self.simulation_failed) self.currentDataset = None # sim setup viewer self.targetView = SimulatorView(self) self.targetView.setModel(self.sim.target_model) self.targetView.expanded.connect(self.target_view_changed) self.targetView.collapsed.connect(self.target_view_changed) # sim results viewer self.result_view = QtGui.QTreeView() # the docking area allows to rearrange the user interface at runtime self.area = pg.dockarea.DockArea() # Window properties self.setCentralWidget(self.area) self.resize(1000, 700) self.setWindowTitle("PyMoskito") res_path = get_resource("mosquito.png") icon = QtGui.QIcon(res_path) self.setWindowIcon(icon) # create docks self.propertyDock = pg.dockarea.Dock("Properties") self.vtkDock = pg.dockarea.Dock("Simulation") self.regimeDock = pg.dockarea.Dock("Regimes") self.dataDock = pg.dockarea.Dock("Data") self.logDock = pg.dockarea.Dock("Log") self.plotDocks = [] self.plotDocks.append(pg.dockarea.Dock("Placeholder")) self.plotWidgets = [] self.timeLines = [] # arrange docks self.area.addDock(self.vtkDock, "right") self.area.addDock(self.regimeDock, "left", self.vtkDock) self.area.addDock(self.propertyDock, "bottom", self.regimeDock) self.area.addDock(self.dataDock, "bottom", self.propertyDock) self.area.addDock(self.plotDocks[-1], "bottom", self.vtkDock) self.area.addDock(self.logDock, "bottom", self.dataDock) # add widgets to the docks self.propertyDock.addWidget(self.targetView) # vtk window self.vtkLayout = QtGui.QVBoxLayout() self.frame = QtGui.QFrame() self.vtkWidget = QVTKRenderWindowInteractor(self.frame) self.vtkLayout.addWidget(self.vtkWidget) self.frame.setLayout(self.vtkLayout) self.vtkDock.addWidget(self.frame) self.vtk_renderer = vtk.vtkRenderer() self.vtkWidget.GetRenderWindow().AddRenderer(self.vtk_renderer) # check if there is a registered visualizer available_vis = get_registered_visualizers() self._logger.info("found visualizers: {}".format( [name for cls, name in available_vis])) if available_vis: # instantiate the first one self._logger.info("loading visualizer '{}'".format( available_vis[0][1])) self.visualizer = available_vis[0][0](self.vtk_renderer) self.vtkWidget.Initialize() else: self.visualizer = None # regime window self.regime_list = QtGui.QListWidget(self) self.regime_list.setSelectionMode( QtGui.QAbstractItemView.ExtendedSelection) self.regimeDock.addWidget(self.regime_list) self.regime_list.itemDoubleClicked.connect(self.regime_dclicked) self._regimes = [] self.regime_file_name = "" # data window self.dataList = QtGui.QListWidget(self) self.dataDock.addWidget(self.dataList) self.dataList.itemDoubleClicked.connect(self.create_plot) # actions for simulation control self.actSimulate = QtGui.QAction(self) self.actSimulate.setText("Simulate") self.actSimulate.setIcon(QtGui.QIcon(get_resource("simulate.png"))) self.actSimulate.triggered.connect(self.start_simulation) # actions for animation control self.actPlayPause = QtGui.QAction(self) self.actPlayPause.setText("Play") self.actPlayPause.setIcon(QtGui.QIcon(get_resource("play.png"))) self.actPlayPause.setDisabled(True) self.actPlayPause.triggered.connect(self.play_animation) self.actStop = QtGui.QAction(self) self.actStop.setText("Stop") self.actStop.setIcon(QtGui.QIcon(get_resource("stop.png"))) self.actStop.setDisabled(True) self.actStop.triggered.connect(self.stop_animation) self.speedDial = QtGui.QDial() self.speedDial.setDisabled(True) self.speedDial.setMinimum(0) self.speedDial.setMaximum(100) self.speedDial.setValue(50) self.speedDial.setSingleStep(1) self.speedDial.resize(24, 24) self.speedDial.valueChanged.connect(self.update_playback_gain) self.timeSlider = QtGui.QSlider(QtCore.Qt.Horizontal, self) self.timeSlider.setMinimum(0) self.timeSliderRange = 1000 self.timeSlider.setMaximum(self.timeSliderRange) self.timeSlider.setTickInterval(1) self.timeSlider.setTracking(True) self.timeSlider.setDisabled(True) self.timeSlider.valueChanged.connect(self.update_playback_time) self.playbackTime = 0 self.playbackGain = 1 self.currentStepSize = 0 self.currentEndTime = 0 self.playbackTimer = QTimer() self.playbackTimer.timeout.connect(self.increment_playback_time) self.playbackTimeChanged.connect(self.update_gui) self.playbackTimeout = 33 # in [ms] -> 30 fps self.actSave = QtGui.QAction(self) self.actSave.setText('Save') self.actSave.setIcon(QtGui.QIcon(get_resource("save.png"))) self.actSave.setDisabled(True) self.actSave.triggered.connect(self.export_simulation_data) self.actLoadRegimes = QtGui.QAction(self) self.actLoadRegimes.setText("load regimes") self.actLoadRegimes.setIcon(QtGui.QIcon(get_resource("load.png"))) self.actLoadRegimes.setDisabled(False) self.actLoadRegimes.triggered.connect(self.load_regime_dialog) self.actExecuteRegimes = QtGui.QAction(self) self.actExecuteRegimes.setText("execute all regimes") self.actExecuteRegimes.setIcon( QtGui.QIcon(get_resource("execute_regimes.png"))) self.actExecuteRegimes.setDisabled(True) self.actExecuteRegimes.triggered.connect(self.execute_regimes_clicked) self.actPostprocessing = QtGui.QAction(self) self.actPostprocessing.setText("launch postprocessor") self.actPostprocessing.setIcon( QtGui.QIcon(get_resource("processing.png"))) self.actPostprocessing.setDisabled(False) self.actPostprocessing.triggered.connect(self.postprocessing_clicked) self.act_reset_camera = QtGui.QAction(self) self.act_reset_camera.setText("reset camera") self.act_reset_camera.setIcon( QtGui.QIcon(get_resource("reset_camera.png"))) self.act_reset_camera.setDisabled(not self.visualizer.can_reset_view) self.act_reset_camera.triggered.connect(self.reset_camera_clicked) # toolbar for control self.toolbarSim = QtGui.QToolBar("Simulation") self.toolbarSim.setIconSize(QtCore.QSize(32, 32)) self.addToolBar(self.toolbarSim) self.toolbarSim.addAction(self.actLoadRegimes) self.toolbarSim.addAction(self.actSave) self.toolbarSim.addSeparator() self.toolbarSim.addAction(self.actSimulate) self.toolbarSim.addAction(self.actExecuteRegimes) self.toolbarSim.addSeparator() self.toolbarSim.addAction(self.actPlayPause) self.toolbarSim.addAction(self.actStop) self.toolbarSim.addWidget(self.speedDial) self.toolbarSim.addWidget(self.timeSlider) self.toolbarSim.addAction(self.actPostprocessing) self.toolbarSim.addAction(self.act_reset_camera) self.postprocessor = None # regime management self.runningBatch = False self._current_regime_index = 0 self._regimes = [] self.regimeFinished.connect(self.run_next_regime) self.finishedRegimeBatch.connect(self.regime_batch_finished) # log dock self.logBox = QPlainTextEditLogger(self) self.logBox.setLevel(logging.INFO) formatter = logging.Formatter( fmt="%(asctime)s - %(name)s - %(levelname)s - %(message)s", datefmt="%H:%M:%S") self.logBox.setFormatter(formatter) self.log_filter = PostFilter(invert=True) self.logBox.addFilter(self.log_filter) logging.getLogger().addHandler(self.logBox) self.logDock.addWidget(self.logBox.widget) # status bar self.status = QtGui.QStatusBar(self) self.setStatusBar(self.status) self.statusLabel = QtGui.QLabel("Ready.") self.statusBar().addPermanentWidget(self.statusLabel) self.timeLabel = QtGui.QLabel("current time: 0.0") self.statusBar().addPermanentWidget(self.timeLabel) # shortcuts self.delShort = QtGui.QShortcut( QtGui.QKeySequence(QtCore.Qt.Key_Delete), self.regime_list) self.delShort.activated.connect(self.remove_regime_items) self.shortOpenRegime = QtGui.QShortcut(QtGui.QKeySequence.Open, self) self.shortOpenRegime.activated.connect(self.load_regime_dialog) self.shortSaveResult = QtGui.QShortcut(QtGui.QKeySequence.Save, self) self.shortSaveResult.activated.connect(self.export_simulation_data) self.shortSaveResult.setEnabled(False) self.shortSpeedUp = QtGui.QShortcut(QtGui.QKeySequence.ZoomIn, self) self.shortSpeedUp.activated.connect(self.increment_playback_speed) self.shortSpeedDown = QtGui.QShortcut(QtGui.QKeySequence.ZoomOut, self) self.shortSpeedDown.activated.connect(self.decrement_playback_speed) self.shortSpeedReset = QtGui.QShortcut( QtGui.QKeySequence(QtCore.Qt.CTRL + QtCore.Qt.Key_0), self) self.shortSpeedReset.activated.connect(self.reset_playback_speed) self.shortRunSimulation = QtGui.QShortcut(QtGui.QKeySequence('F5'), self) self.shortRunSimulation.activated.connect(self.start_simulation) self.shortRunRegimeBatch = QtGui.QShortcut(QtGui.QKeySequence('F6'), self) self.shortRunRegimeBatch.activated.connect( self.execute_regimes_clicked) self.shortRunPostprocessing = QtGui.QShortcut(QtGui.QKeySequence('F7'), self) self.shortRunPostprocessing.activated.connect( self.postprocessing_clicked) self.shortPlayPause = QtGui.QShortcut( QtGui.QKeySequence(QtCore.Qt.Key_Space), self) self.shortPlayPause.activated.connect(self.play_animation) self.shortPlayPause.setEnabled(False) self.postprocessor = None self._logger.info("Simulation GUI is up and running.") def set_visualizer(self, vis): self.visualizer = vis self.vtkWidget.Initialize() def play_animation(self): """ play the animation """ # self.statusLabel.setText('playing animation') self.actPlayPause.setText("Pause") self.actPlayPause.setIcon(QtGui.QIcon(get_resource("pause.png"))) self.actPlayPause.triggered.disconnect(self.play_animation) self.actPlayPause.triggered.connect(self.pause_animation) self.shortPlayPause.activated.disconnect(self.play_animation) self.shortPlayPause.activated.connect(self.pause_animation) self.playbackTimer.start(self.playbackTimeout) def pause_animation(self): """ pause the animation """ # self.statusLabel.setText('pausing animation') self.playbackTimer.stop() self.actPlayPause.setText("Play") self.actPlayPause.setIcon(QtGui.QIcon(get_resource("play.png"))) self.actPlayPause.triggered.disconnect(self.pause_animation) self.actPlayPause.triggered.connect(self.play_animation) self.shortPlayPause.activated.disconnect(self.pause_animation) self.shortPlayPause.activated.connect(self.play_animation) def stop_animation(self): """ pause the animation """ # self.statusLabel.setText('stopping animation') if self.actPlayPause.text() == "Pause": # animation is playing -> stop it self.playbackTimer.stop() self.actPlayPause.setText("Play") self.actPlayPause.setIcon(QtGui.QIcon(get_resource("play.png"))) self.actPlayPause.triggered.disconnect(self.pause_animation) self.actPlayPause.triggered.connect(self.play_animation) self.shortPlayPause.activated.disconnect(self.pause_animation) self.shortPlayPause.activated.connect(self.play_animation) self.timeSlider.setValue(0) def start_simulation(self): """ start the simulation and disable start button """ regime_name = str( self.regime_list.item(self._current_regime_index).text()) self.statusLabel.setText(u"simulating {}".format(regime_name)) self._logger.info(u"Simulating: {}".format(regime_name)) self.actSimulate.setDisabled(True) self.shortRunSimulation.setEnabled(False) self.shortRunRegimeBatch.setEnabled(False) self.actExecuteRegimes.setDisabled(True) self.guiProgress = QtGui.QProgressBar(self) self.sim.simulationProgressChanged.connect(self.guiProgress.setValue) self.statusBar().addWidget(self.guiProgress) self.runSimulation.emit() def export_simulation_data(self, ok): """ query the user for a custome name and export the current simulation results :param ok: unused parameter from QAction.triggered() Signal """ res, ok = QtGui.QInputDialog.getText( self, u"PyMoskito", u"Please specify regime a name", QtGui.QLineEdit.Normal, self._regimes[self._current_regime_index]["Name"]) if not ok: return name = unicode(res.toUtf8(), encoding="utf-8") if not name: self._logger.warning(u"empty regime name specified!") self._save_data(name) def _save_data(self, name): """ save current data-set :param name: name of the file """ self.currentDataset.update({"regime name": name}) path = os.path.join(os.path.pardir, "results", "simulation", self.regime_file_name) # check for path existence if not os.path.isdir(path): os.makedirs(path) # pmr - PyMoskito Result file_name = os.path.join( path, time.strftime("%Y%m%d-%H%M%S") + "_" + name + ".pmr") with open(file_name.encode("utf-8"), "wb") as f: cPickle.dump(self.currentDataset, f, protocol=2) self.statusLabel.setText(u"results saved to {}".format(file_name)) self._logger.info(u"results saved to {}".format(file_name)) def load_regime_dialog(self): regime_path = os.path.join("../regimes/") file_name = unicode(QtGui.QFileDialog.getOpenFileName( self, "Open Regime File", regime_path, "Simulation Regime files (*.sreg)").toUtf8(), encoding="utf-8") if not file_name: return self.load_regimes_from_file(file_name) def load_regimes_from_file(self, file_name): """ load simulation regime from file :param file_name: """ self.regime_file_name = os.path.split(file_name)[-1][:-5] self._logger.info(u"loading regime file: {0}".format( self.regime_file_name)) with open(file_name.encode("utf-8"), "r") as f: self._regimes += yaml.load(f) self._update_regime_list() if self._regimes: self.actExecuteRegimes.setDisabled(False) self._logger.info("loaded {} regimes".format(len(self._regimes))) self.statusBar().showMessage( u"loaded {} regimes.".format(len(self._regimes)), 1000) return def _update_regime_list(self): self.regime_list.clear() for reg in self._regimes: self._logger.debug("adding '{}' to regime list".format( reg["Name"])) self.regime_list.addItem(reg["Name"]) def remove_regime_items(self): if self.regime_list.currentRow() >= 0: # flag all selected files as invalid items = self.regime_list.selectedItems() for item in items: del self._regimes[self.regime_list.row(item)] self.regime_list.takeItem(self.regime_list.row(item)) def regime_dclicked(self, item): """ applies the selected regime to the current target :param item: """ self.apply_regime_by_name(str(item.text())) def apply_regime_by_name(self, regime_name): """ :param regime_name: :return: """ # get regime idx try: idx = map(itemgetter("Name"), self._regimes).index(regime_name) except ValueError as e: self._logger.info( "apply_regime_by_name(): Error no regime called {0}".format( regime_name)) return # apply self._apply_regime_by_idx(idx) def _apply_regime_by_idx(self, index=0): if index >= len(self._regimes): self._logger.error("applyRegime: index error! ({})".format(index)) return reg_name = self._regimes[index]["Name"] self.statusBar().showMessage("regime {} applied.".format(reg_name), 1000) self._logger.info("applying regime '{}'".format(reg_name)) self._current_regime_index = index self.sim.set_regime(self._regimes[index]) def execute_regimes_clicked(self): """ execute all regimes in the current list """ self.runningBatch = True self._current_regime_index = -1 self.regimeFinished.emit() def run_next_regime(self): """ executes the next regime """ # are we finished? if self._current_regime_index == len(self._regimes) - 1: self.finishedRegimeBatch.emit() return self._apply_regime_by_idx(self._current_regime_index + 1) self.start_simulation() def regime_batch_finished(self): self.runningBatch = False self.actExecuteRegimes.setDisabled(False) # self._current_regime_index = 0 self.statusLabel.setText("All regimes have been simulated!") self.actSave.setDisabled(True) def simulation_finished(self, data): """ main hook to be called by the simulation interface if integration is finished integration finished, enable play button and update plots :param data: dict with simulation data """ self._logger.info(u"simulation finished") self.statusLabel.setText(u"simulation finished.") self.actSimulate.setDisabled(False) self.shortRunSimulation.setEnabled(True) self.shortRunRegimeBatch.setEnabled(True) self.actPlayPause.setDisabled(False) self.shortPlayPause.setEnabled(True) self.shortSaveResult.setEnabled(True) self.actStop.setDisabled(False) self.actSave.setDisabled(False) self.speedDial.setDisabled(False) self.timeSlider.setDisabled(False) self.sim.simulationProgressChanged.disconnect( self.guiProgress.setValue) self.statusBar().removeWidget(self.guiProgress) self.timeSlider.triggerAction(QtGui.QAbstractSlider.SliderToMinimum) self.currentDataset = data self._read_results() self._update_data_list() self._update_plots() self.stop_animation() # self.playAnimation() if self.runningBatch: regime_name = self._regimes[self._current_regime_index]["Name"] self._save_data(regime_name) self.regimeFinished.emit() else: self.actExecuteRegimes.setDisabled(False) def simulation_failed(self, data): """ integration failed, enable play button and update plots :param data: """ self.statusLabel.setText(u"simulation failed!") self.simulation_finished(data) def _read_results(self): self.currentStepSize = 1.0 / self.currentDataset["results"][ "Simulation"]['measure rate'] self.currentEndTime = self.currentDataset["results"]["Simulation"][ "end time"] self.validData = True def add_plot_to_dock(self, plot_widget): self.d3.addWidget(plot_widget) def increment_playback_speed(self): self.speedDial.setValue(self.speedDial.value() + self.speedDial.singleStep()) def decrement_playback_speed(self): self.speedDial.setValue(self.speedDial.value() - self.speedDial.singleStep()) def reset_playback_speed(self): self.speedDial.setValue(self.speedDial.range() / 2) def increment_playback_time(self): """ go one time step forward in playback """ increment = self.playbackGain * self.playbackTimeout / 1000 if self.playbackTime + increment <= self.currentEndTime: self.playbackTime += increment pos = self.playbackTime / self.currentEndTime * self.timeSliderRange self.timeSlider.blockSignals(True) self.timeSlider.setValue(pos) self.timeSlider.blockSignals(False) self.playbackTimeChanged.emit() else: self.pause_animation() return def update_playback_gain(self, val): """ adjust playback time to slider value :param val: """ self.playbackGain = 10**(5.0 * (val - self.speedDial.maximum() / 2) / self.speedDial.maximum()) def update_playback_time(self): """ adjust playback time to slider value """ self.playbackTime = self.timeSlider.value( ) / self.timeSliderRange * self.currentEndTime self.playbackTimeChanged.emit() return def update_gui(self): """ updates the graphical user interface, including: - timestamp - visualisation - time cursor in diagrams """ if not self.validData: return self.timeLabel.setText(u"current time: %4f" % self.playbackTime) # update time cursor in plots self._update_time_cursor() # update state of rendering if self.visualizer: state = self.interpolate(self.currentDataset["results"]["Solver"]) self.visualizer.update_scene(state) self.vtkWidget.GetRenderWindow().Render() def interpolate(self, data): """ find corresponding item in data-set that fits the current playback time :param data: data-set in which the correct datum has to be found :return: datum fitting the current playback time """ idx = next(( index for index, val in enumerate(self.currentDataset["results"]["time"]) if val >= self.playbackTime), None) if idx is None: self._logger.info( u"interpolate(): Error no entry found for t={0}".format( self.playbackTime)) return None else: if len(data.shape) == 1: return data[idx] elif len(data.shape) == 2: return data[idx, :] else: self._logger.info( u"interpolate(): Error Dimension {0} not understood.". format(data.shape)) return None def _update_data_list(self): self.dataList.clear() for module, results in self.currentDataset["results"].iteritems(): if not isinstance(results, np.ndarray): continue if len(results.shape) == 1: self.dataList.insertItem(0, "{0}".format(module)) elif len(results.shape) == 2: for col in range(results.shape[1]): self.dataList.insertItem(0, "{0}.{1}".format(module, col)) elif len(results.shape) == 3: for col in range(results.shape[1]): self.dataList.insertItem(0, "{0}.{1}".format(module, col)) def create_plot(self, item): """ creates a plot widget corresponding to the ListItem :param item: ListItem """ title = str(item.text()) data = self._get_data_by_name(title) t = self.currentDataset["results"]["time"] dock = pg.dockarea.Dock(title) self.area.addDock(dock, "above", self.plotDocks[-1]) widget = pg.PlotWidget(title=title) widget.plot(x=t, y=data) time_line = pg.InfiniteLine(self.playbackTime, angle=90, movable=False, pen=pg.mkPen("#FF0000", width=2.0)) widget.getPlotItem().addItem(time_line) # enable grid widget.showGrid(True, True) dock.addWidget(widget) self.plotDocks.append(dock) self.plotWidgets.append(widget) self.timeLines.append(time_line) def _get_data_by_name(self, name): tmp = name.split(".") if len(tmp) == 1: data = self.currentDataset["results"][tmp[0]] # if the data-set contains 1d array -> convert to float data = [float(x) for x in data] elif len(tmp) == 2: if len(self.currentDataset["results"][tmp[0]].shape) == 2: data = self.currentDataset["results"][tmp[0]][:, tmp[1]] if len(self.currentDataset["results"][tmp[0]].shape ) == 3: # data-set has the structure [ [[1]], [[2]] ] data = self.currentDataset['results'][tmp[0]][..., tmp[1], 0] return data def _update_time_cursor(self): """ updates the time lines of all plot windows """ for line in self.timeLines: line.setValue(self.playbackTime) def _update_plots(self): """ plot the fresh simulation data """ for dock in self.plotDocks: for widget in dock.widgets: if not self.dataList.findItems(dock.name(), QtCore.Qt.MatchExactly): # no data for this plot -> reset it widget.getPlotItem().clear() # TODO remove tab from dock and del instance else: widget.getPlotItem().clear() x_data = self.currentDataset["results"]["time"] y_data = self._get_data_by_name(dock.name()) widget.getPlotItem().plot(x=x_data, y=y_data) def target_view_changed(self, index): self.targetView.resizeColumnToContents(0) def postprocessing_clicked(self): """ starts the post- and metaprocessing application """ self._logger.info(u"launching postprocessor") self.statusBar().showMessage(u"launching postprocessor", 1000) if self.postprocessor is None: self.postprocessor = PostProcessor() self.postprocessor.show() def reset_camera_clicked(self): """ reset camera in vtk window """ self.visualizer.reset_camera() self.vtkWidget.GetRenderWindow().Render()
class PostProcessor(QtGui.QMainWindow): sim_results_changed = pyqtSignal() post_results_changed = pyqtSignal() figures_changed = pyqtSignal(list, str) def __init__(self, parent=None): QtGui.QMainWindow.__init__(self, parent) self._logger = logging.getLogger(self.__class__.__name__) self.setWindowTitle(u"Processing") self.setWindowIcon(QtGui.QIcon(get_resource("processing.png"))) self.mainFrame = QtGui.QWidget(self) self.resize(1000, 600) # toolbar self.toolBar = QtGui.QToolBar(u"file control") self.toolBar.setIconSize(QtCore.QSize(24, 24)) self.addToolBar(self.toolBar) self.actLoad = QtGui.QAction(self) self.actLoad.setText(u"load result file") self.actLoad.setIcon(QtGui.QIcon(get_resource("load.png"))) self.actLoad.setDisabled(False) self.actLoad.triggered.connect(self.load_result_files) self.actPostLoad = QtGui.QAction(self) self.actPostLoad.setText(u"load post-result file") self.actPostLoad.setIcon(QtGui.QIcon(get_resource("load.png"))) self.actPostLoad.setDisabled(False) self.actPostLoad.triggered.connect(self.load_post_result_files) self.actSwitch = QtGui.QAction(self) self.actSwitch.setText(u"switch display mode") self.actSwitch.setIcon(QtGui.QIcon(get_resource("left_mode.png"))) self.actSwitch.setDisabled(False) self.actSwitch.triggered.connect(self.switch_sides) self.displayLeft = True self.spacer1 = QtGui.QWidget() self.spacer2 = QtGui.QWidget() self.spacer1.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) self.spacer2.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) self.actReloadMethods = QtGui.QAction(self) self.actReloadMethods.setText(u"reload methods") self.actReloadMethods.setIcon(QtGui.QIcon(get_resource("reload.png"))) self.actReloadMethods.setDisabled(False) self.actReloadMethods.triggered.connect(self.update_post_method_list) self.actReloadMetaMethods = QtGui.QAction(self) self.actReloadMetaMethods.setText(u"reload meta methods") self.actReloadMetaMethods.setIcon( QtGui.QIcon(get_resource("reload.png"))) self.actReloadMetaMethods.setDisabled(False) self.actReloadMetaMethods.triggered.connect( self.update_meta_method_list) self.toolBar.addAction(self.actLoad) self.toolBar.addAction(self.actReloadMethods) self.toolBar.addWidget(self.spacer1) self.toolBar.addAction(self.actSwitch) self.toolBar.addWidget(self.spacer2) self.toolBar.addAction(self.actReloadMetaMethods) self.toolBar.addAction(self.actPostLoad) # main window self.grid = QtGui.QGridLayout(self.mainFrame) self.grid.setColumnMinimumWidth(0, 70) self.grid.setColumnStretch(0, 0) self.grid.setColumnStretch(1, 1) self.methodList = QtGui.QListWidget(self) self.methodList.itemDoubleClicked.connect(self.post_processor_clicked) self.update_post_method_list() self.metaMethodList = QtGui.QListWidget(self) self.metaMethodList.itemDoubleClicked.connect( self.meta_processor_clicked) self.update_meta_method_list() self.sim_result_list = QtGui.QListWidget(self) self.sim_results_changed.connect(self.update_result_list) self.results = [] self.delShort = QtGui.QShortcut( QtGui.QKeySequence(QtCore.Qt.Key_Delete), self.sim_result_list) self.delShort.activated.connect(self.remove_result_item) # figures self._figure_dict = {} self.figures_changed.connect(self.update_figure_lists) self.post_figure_list = QtGui.QListWidget(self) self.post_figure_list.currentItemChanged.connect( self.current_figure_changed) self.meta_figure_list = QtGui.QListWidget(self) self.meta_figure_list.currentItemChanged.connect( self.current_figure_changed) self.plotView = QtGui.QWidget() self.lastFigure = None self.post_result_list = QtGui.QListWidget(self) self.post_results_changed.connect(self.update_post_result_list) self.post_results = [] self.delShortPost = QtGui.QShortcut( QtGui.QKeySequence(QtCore.Qt.Key_Backspace), self.post_result_list) self.delShortPost.activated.connect(self.remove_post_result_item) # log window self.log_list = QPlainTextEditLogger(self) self.log_list.setLevel(logging.INFO) formatter = logging.Formatter( fmt="%(asctime)s - %(name)s - %(levelname)s - %(message)s", datefmt="%H:%M:%S") self.filter = PostFilter() self.log_list.addFilter(self.filter) self.log_list.setFormatter(formatter) logging.getLogger().addHandler(self.log_list) self.grid.addWidget(QtGui.QLabel(u"result files:"), 0, 0) self.grid.addWidget(self.sim_result_list, 1, 0) self.grid.addWidget(QtGui.QLabel(u"Postprocessors:"), 2, 0) self.grid.addWidget(self.methodList, 3, 0) self.grid.addWidget(QtGui.QLabel(u"figures:"), 4, 0) self.grid.addWidget(self.post_figure_list, 5, 0) self.grid.addWidget(QtGui.QLabel(u"selected figure:"), 0, 1) self.grid.addWidget(QtGui.QLabel(u"postprocessor files:"), 0, 2) self.grid.addWidget(self.post_result_list, 1, 2) self.grid.addWidget(QtGui.QLabel(u"Metaprocessors:"), 2, 2) self.grid.addWidget(self.metaMethodList, 3, 2) self.grid.addWidget(QtGui.QLabel(u"figures:"), 4, 2) self.grid.addWidget(self.meta_figure_list, 5, 2) self.grid.addWidget(self.log_list.widget, 6, 0, 1, 3) self.mainFrame.setLayout(self.grid) self.setCentralWidget(self.mainFrame) # status bar self.statusBar = QtGui.QStatusBar(self) self.setStatusBar(self.statusBar) # self.statusBar.addPermanentWidget(self.log_list.widget) # load test results # filePath = os.path.join(os.pardir, 'results', 'simulation', 'default', 'example.pmr') # self._loadResultFile(filePath) # # load test post-results # filePath = os.path.join(os.pardir, 'results', 'postprocessing', 'default') # self._loadResultFile(filePath) def load_result_files(self): path = os.path.join(os.path.pardir, "results", "simulation") dialog = QtGui.QFileDialog(self) dialog.setFileMode(QtGui.QFileDialog.ExistingFiles) dialog.setDirectory(path) dialog.setNameFilter("PyMoskito Result files (*.pmr)") files = None if dialog.exec_(): files = dialog.selectedFiles() if files: for single_file in files: self._load_result_file( unicode(single_file.toUtf8(), encoding="utf-8")) def _load_result_file(self, file_name): """ loads a result file """ self._logger.info(u"loading result file {}".format(file_name)) with open(file_name.encode("utf-8"), "rb") as f: self.results.append(cPickle.load(f)) self.sim_results_changed.emit() def update_result_list(self): self.sim_result_list.clear() for res in self.results: name = res["regime name"] self.sim_result_list.addItem(name) def remove_result_item(self): if self.sim_result_list.currentRow() >= 0: del self.results[self.sim_result_list.currentRow()] self.sim_result_list.takeItem(self.sim_result_list.currentRow()) def load_post_result_files(self): path = os.path.join(os.path.pardir, "results", "processing") dialog = QtGui.QFileDialog(self) dialog.setFileMode(QtGui.QFileDialog.ExistingFiles) dialog.setDirectory(path) dialog.setNameFilter("Postprocessing Output files (*.pof)") files = None if dialog.exec_(): files = dialog.selectedFiles() if files: for selectedFile in files: self._load_post_result_file( unicode(selectedFile.toUtf8(), encoding="utf-8")) def _load_post_result_file(self, file_name): """ loads a post-result file (.pof) """ name = os.path.split(file_name)[-1][:-4] with open(file_name, "r") as f: results = cPickle.load(f) results.update({"name": name}) self.post_results.append(results) self.post_results_changed.emit() def update_post_result_list(self): self.post_result_list.clear() for res in self.post_results: name = res["name"] self.post_result_list.addItem(name) def remove_post_result_item(self): if self.post_result_list.currentRow() >= 0: del self.post_results[self.post_result_list.currentRow()] self.post_result_list.takeItem(self.post_result_list.currentRow()) def update_post_method_list(self): self.methodList.clear() modules = pm.get_registered_processing_modules(PostProcessingModule) for module in modules: self.methodList.addItem(module[1]) def update_meta_method_list(self): self.metaMethodList.clear() modules = pm.get_registered_processing_modules(MetaProcessingModule) for module in modules: self.metaMethodList.addItem(module[1]) def post_processor_clicked(self, item): self.run_processor(str(item.text()), "post") def meta_processor_clicked(self, item): self.run_processor(str(item.text()), "meta") def run_processor(self, name, processor_type): if processor_type == "post": result_files = self.results base_cls = PostProcessingModule elif processor_type == "meta": result_files = self.post_results base_cls = MetaProcessingModule else: self._logger.error( u"unknown processor type {0}".format(processor_type)) raise ValueError( "unknown processor type {0}".format(processor_type)) if not result_files: self._logger.warning( "run_processor() Error: no result file loaded!") return processor_cls = pm.get_processing_module_class_by_name(base_cls, name) processor = processor_cls() figs = [] try: self._logger.info("executing processor '{0}'".format(name)) figs = processor.process(self.results) except Exception, err: self._logger.exception("Error in processor") self.figures_changed.emit(figs, processor_type) self._logger.info("finished postprocessing")
class SimulationGui(QtGui.QMainWindow): """ class for the graphical user interface """ # TODO enable closing plot docks by right-clicking their name # TODO add ability to stop an simulation # TODO add ability to stop regime execution runSimulation = pyqtSignal() playbackTimeChanged = pyqtSignal() regimeFinished = pyqtSignal() finishedRegimeBatch = pyqtSignal() def __init__(self): # constructor of the base class QtGui.QMainWindow.__init__(self) self._logger = logging.getLogger(self.__class__.__name__) # Create Simulation Backend self.guiProgress = None self.cmdProgress = None self.sim = SimulatorInteractor(self) self.runSimulation.connect(self.sim.run_simulation) self.sim.simulation_finished.connect(self.simulation_finished) self.sim.simulation_failed.connect(self.simulation_failed) self.currentDataset = None # sim setup viewer self.targetView = SimulatorView(self) self.targetView.setModel(self.sim.target_model) self.targetView.expanded.connect(self.target_view_changed) self.targetView.collapsed.connect(self.target_view_changed) # sim results viewer self.result_view = QtGui.QTreeView() # the docking area allows to rearrange the user interface at runtime self.area = pg.dockarea.DockArea() # Window properties self.setCentralWidget(self.area) self.resize(1000, 700) self.setWindowTitle("PyMoskito") res_path = get_resource("mosquito.png") icon = QtGui.QIcon(res_path) self.setWindowIcon(icon) # create docks self.propertyDock = pg.dockarea.Dock("Properties") self.vtkDock = pg.dockarea.Dock("Simulation") self.regimeDock = pg.dockarea.Dock("Regimes") self.dataDock = pg.dockarea.Dock("Data") self.logDock = pg.dockarea.Dock("Log") self.plotDocks = [] self.plotDocks.append(pg.dockarea.Dock("Placeholder")) self.plotWidgets = [] self.timeLines = [] # arrange docks self.area.addDock(self.vtkDock, "right") self.area.addDock(self.regimeDock, "left", self.vtkDock) self.area.addDock(self.propertyDock, "bottom", self.regimeDock) self.area.addDock(self.dataDock, "bottom", self.propertyDock) self.area.addDock(self.plotDocks[-1], "bottom", self.vtkDock) self.area.addDock(self.logDock, "bottom", self.dataDock) # add widgets to the docks self.propertyDock.addWidget(self.targetView) # vtk window self.vtkLayout = QtGui.QVBoxLayout() self.frame = QtGui.QFrame() self.vtkWidget = QVTKRenderWindowInteractor(self.frame) self.vtkLayout.addWidget(self.vtkWidget) self.frame.setLayout(self.vtkLayout) self.vtkDock.addWidget(self.frame) self.vtk_renderer = vtk.vtkRenderer() self.vtkWidget.GetRenderWindow().AddRenderer(self.vtk_renderer) # check if there is a registered visualizer available_vis = get_registered_visualizers() self._logger.info("found visualizers: {}".format([name for cls, name in available_vis])) if available_vis: # instantiate the first one self._logger.info("loading visualizer '{}'".format(available_vis[0][1])) self.visualizer = available_vis[0][0](self.vtk_renderer) self.vtkWidget.Initialize() else: self.visualizer = None # regime window self.regime_list = QtGui.QListWidget(self) self.regime_list.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection) self.regimeDock.addWidget(self.regime_list) self.regime_list.itemDoubleClicked.connect(self.regime_dclicked) self._regimes = [] self.regime_file_name = "" # data window self.dataList = QtGui.QListWidget(self) self.dataDock.addWidget(self.dataList) self.dataList.itemDoubleClicked.connect(self.create_plot) # actions for simulation control self.actSimulate = QtGui.QAction(self) self.actSimulate.setText("Simulate") self.actSimulate.setIcon(QtGui.QIcon(get_resource("simulate.png"))) self.actSimulate.triggered.connect(self.start_simulation) # actions for animation control self.actPlayPause = QtGui.QAction(self) self.actPlayPause.setText("Play") self.actPlayPause.setIcon(QtGui.QIcon(get_resource("play.png"))) self.actPlayPause.setDisabled(True) self.actPlayPause.triggered.connect(self.play_animation) self.actStop = QtGui.QAction(self) self.actStop.setText("Stop") self.actStop.setIcon(QtGui.QIcon(get_resource("stop.png"))) self.actStop.setDisabled(True) self.actStop.triggered.connect(self.stop_animation) self.speedDial = QtGui.QDial() self.speedDial.setDisabled(True) self.speedDial.setMinimum(0) self.speedDial.setMaximum(100) self.speedDial.setValue(50) self.speedDial.setSingleStep(1) self.speedDial.resize(24, 24) self.speedDial.valueChanged.connect(self.update_playback_gain) self.timeSlider = QtGui.QSlider(QtCore.Qt.Horizontal, self) self.timeSlider.setMinimum(0) self.timeSliderRange = 1000 self.timeSlider.setMaximum(self.timeSliderRange) self.timeSlider.setTickInterval(1) self.timeSlider.setTracking(True) self.timeSlider.setDisabled(True) self.timeSlider.valueChanged.connect(self.update_playback_time) self.playbackTime = 0 self.playbackGain = 1 self.currentStepSize = 0 self.currentEndTime = 0 self.playbackTimer = QTimer() self.playbackTimer.timeout.connect(self.increment_playback_time) self.playbackTimeChanged.connect(self.update_gui) self.playbackTimeout = 33 # in [ms] -> 30 fps self.actSave = QtGui.QAction(self) self.actSave.setText('Save') self.actSave.setIcon(QtGui.QIcon(get_resource("save.png"))) self.actSave.setDisabled(True) self.actSave.triggered.connect(self.export_simulation_data) self.actLoadRegimes = QtGui.QAction(self) self.actLoadRegimes.setText("load regimes") self.actLoadRegimes.setIcon(QtGui.QIcon(get_resource("load.png"))) self.actLoadRegimes.setDisabled(False) self.actLoadRegimes.triggered.connect(self.load_regime_dialog) self.actExecuteRegimes = QtGui.QAction(self) self.actExecuteRegimes.setText("execute all regimes") self.actExecuteRegimes.setIcon(QtGui.QIcon(get_resource("execute_regimes.png"))) self.actExecuteRegimes.setDisabled(True) self.actExecuteRegimes.triggered.connect(self.execute_regimes_clicked) self.actPostprocessing = QtGui.QAction(self) self.actPostprocessing.setText("launch postprocessor") self.actPostprocessing.setIcon(QtGui.QIcon(get_resource("processing.png"))) self.actPostprocessing.setDisabled(False) self.actPostprocessing.triggered.connect(self.postprocessing_clicked) self.act_reset_camera = QtGui.QAction(self) self.act_reset_camera.setText("reset camera") self.act_reset_camera.setIcon(QtGui.QIcon(get_resource("reset_camera.png"))) self.act_reset_camera.setDisabled(not self.visualizer.can_reset_view) self.act_reset_camera.triggered.connect(self.reset_camera_clicked) # toolbar for control self.toolbarSim = QtGui.QToolBar("Simulation") self.toolbarSim.setIconSize(QtCore.QSize(32, 32)) self.addToolBar(self.toolbarSim) self.toolbarSim.addAction(self.actLoadRegimes) self.toolbarSim.addAction(self.actSave) self.toolbarSim.addSeparator() self.toolbarSim.addAction(self.actSimulate) self.toolbarSim.addAction(self.actExecuteRegimes) self.toolbarSim.addSeparator() self.toolbarSim.addAction(self.actPlayPause) self.toolbarSim.addAction(self.actStop) self.toolbarSim.addWidget(self.speedDial) self.toolbarSim.addWidget(self.timeSlider) self.toolbarSim.addAction(self.actPostprocessing) self.toolbarSim.addAction(self.act_reset_camera) self.postprocessor = None # regime management self.runningBatch = False self._current_regime_index = 0 self._regimes = [] self.regimeFinished.connect(self.run_next_regime) self.finishedRegimeBatch.connect(self.regime_batch_finished) # log dock self.logBox = QPlainTextEditLogger(self) self.logBox.setLevel(logging.INFO) formatter = logging.Formatter(fmt="%(asctime)s - %(name)s - %(levelname)s - %(message)s", datefmt="%H:%M:%S") self.logBox.setFormatter(formatter) self.log_filter = PostFilter(invert=True) self.logBox.addFilter(self.log_filter) logging.getLogger().addHandler(self.logBox) self.logDock.addWidget(self.logBox.widget) # status bar self.status = QtGui.QStatusBar(self) self.setStatusBar(self.status) self.statusLabel = QtGui.QLabel("Ready.") self.statusBar().addPermanentWidget(self.statusLabel) self.timeLabel = QtGui.QLabel("current time: 0.0") self.statusBar().addPermanentWidget(self.timeLabel) # shortcuts self.delShort = QtGui.QShortcut(QtGui.QKeySequence(QtCore.Qt.Key_Delete), self.regime_list) self.delShort.activated.connect(self.remove_regime_items) self.shortOpenRegime = QtGui.QShortcut(QtGui.QKeySequence.Open, self) self.shortOpenRegime.activated.connect(self.load_regime_dialog) self.shortSaveResult = QtGui.QShortcut(QtGui.QKeySequence.Save, self) self.shortSaveResult.activated.connect(self.export_simulation_data) self.shortSaveResult.setEnabled(False) self.shortSpeedUp = QtGui.QShortcut(QtGui.QKeySequence.ZoomIn, self) self.shortSpeedUp.activated.connect(self.increment_playback_speed) self.shortSpeedDown = QtGui.QShortcut(QtGui.QKeySequence.ZoomOut, self) self.shortSpeedDown.activated.connect(self.decrement_playback_speed) self.shortSpeedReset = QtGui.QShortcut(QtGui.QKeySequence(QtCore.Qt.CTRL + QtCore.Qt.Key_0), self) self.shortSpeedReset.activated.connect(self.reset_playback_speed) self.shortRunSimulation = QtGui.QShortcut(QtGui.QKeySequence('F5'), self) self.shortRunSimulation.activated.connect(self.start_simulation) self.shortRunRegimeBatch = QtGui.QShortcut(QtGui.QKeySequence('F6'), self) self.shortRunRegimeBatch.activated.connect(self.execute_regimes_clicked) self.shortRunPostprocessing = QtGui.QShortcut(QtGui.QKeySequence('F7'), self) self.shortRunPostprocessing.activated.connect(self.postprocessing_clicked) self.shortPlayPause = QtGui.QShortcut(QtGui.QKeySequence(QtCore.Qt.Key_Space), self) self.shortPlayPause.activated.connect(self.play_animation) self.shortPlayPause.setEnabled(False) self.postprocessor = None self._logger.info("Simulation GUI is up and running.") def set_visualizer(self, vis): self.visualizer = vis self.vtkWidget.Initialize() def play_animation(self): """ play the animation """ # self.statusLabel.setText('playing animation') self.actPlayPause.setText("Pause") self.actPlayPause.setIcon(QtGui.QIcon(get_resource("pause.png"))) self.actPlayPause.triggered.disconnect(self.play_animation) self.actPlayPause.triggered.connect(self.pause_animation) self.shortPlayPause.activated.disconnect(self.play_animation) self.shortPlayPause.activated.connect(self.pause_animation) self.playbackTimer.start(self.playbackTimeout) def pause_animation(self): """ pause the animation """ # self.statusLabel.setText('pausing animation') self.playbackTimer.stop() self.actPlayPause.setText("Play") self.actPlayPause.setIcon(QtGui.QIcon(get_resource("play.png"))) self.actPlayPause.triggered.disconnect(self.pause_animation) self.actPlayPause.triggered.connect(self.play_animation) self.shortPlayPause.activated.disconnect(self.pause_animation) self.shortPlayPause.activated.connect(self.play_animation) def stop_animation(self): """ pause the animation """ # self.statusLabel.setText('stopping animation') if self.actPlayPause.text() == "Pause": # animation is playing -> stop it self.playbackTimer.stop() self.actPlayPause.setText("Play") self.actPlayPause.setIcon(QtGui.QIcon(get_resource("play.png"))) self.actPlayPause.triggered.disconnect(self.pause_animation) self.actPlayPause.triggered.connect(self.play_animation) self.shortPlayPause.activated.disconnect(self.pause_animation) self.shortPlayPause.activated.connect(self.play_animation) self.timeSlider.setValue(0) def start_simulation(self): """ start the simulation and disable start button """ regime_name = str(self.regime_list.item(self._current_regime_index).text()) self.statusLabel.setText(u"simulating {}".format(regime_name)) self._logger.info(u"Simulating: {}".format(regime_name)) self.actSimulate.setDisabled(True) self.shortRunSimulation.setEnabled(False) self.shortRunRegimeBatch.setEnabled(False) self.actExecuteRegimes.setDisabled(True) self.guiProgress = QtGui.QProgressBar(self) self.sim.simulationProgressChanged.connect(self.guiProgress.setValue) self.statusBar().addWidget(self.guiProgress) self.runSimulation.emit() def export_simulation_data(self, ok): """ query the user for a custome name and export the current simulation results :param ok: unused parameter from QAction.triggered() Signal """ res, ok = QtGui.QInputDialog.getText(self, u"PyMoskito", u"Please specify regime a name", QtGui.QLineEdit.Normal, self._regimes[self._current_regime_index]["Name"]) if not ok: return name = unicode(res.toUtf8(), encoding="utf-8") if not name: self._logger.warning(u"empty regime name specified!") self._save_data(name) def _save_data(self, name): """ save current data-set :param name: name of the file """ self.currentDataset.update({"regime name": name}) path = os.path.join(os.path.pardir, "results", "simulation", self.regime_file_name) # check for path existence if not os.path.isdir(path): os.makedirs(path) # pmr - PyMoskito Result file_name = os.path.join(path, time.strftime("%Y%m%d-%H%M%S") + "_" + name + ".pmr") with open(file_name.encode("utf-8"), "wb") as f: cPickle.dump(self.currentDataset, f, protocol=2) self.statusLabel.setText(u"results saved to {}".format(file_name)) self._logger.info(u"results saved to {}".format(file_name)) def load_regime_dialog(self): regime_path = os.path.join("../regimes/") file_name = unicode(QtGui.QFileDialog.getOpenFileName(self, "Open Regime File", regime_path, "Simulation Regime files (*.sreg)").toUtf8(), encoding="utf-8") if not file_name: return self.load_regimes_from_file(file_name) def load_regimes_from_file(self, file_name): """ load simulation regime from file :param file_name: """ self.regime_file_name = os.path.split(file_name)[-1][:-5] self._logger.info(u"loading regime file: {0}".format(self.regime_file_name)) with open(file_name.encode("utf-8"), "r") as f: self._regimes += yaml.load(f) self._update_regime_list() if self._regimes: self.actExecuteRegimes.setDisabled(False) self._logger.info("loaded {} regimes".format(len(self._regimes))) self.statusBar().showMessage(u"loaded {} regimes.".format(len(self._regimes)), 1000) return def _update_regime_list(self): self.regime_list.clear() for reg in self._regimes: self._logger.debug("adding '{}' to regime list".format(reg["Name"])) self.regime_list.addItem(reg["Name"]) def remove_regime_items(self): if self.regime_list.currentRow() >= 0: # flag all selected files as invalid items = self.regime_list.selectedItems() for item in items: del self._regimes[self.regime_list.row(item)] self.regime_list.takeItem(self.regime_list.row(item)) def regime_dclicked(self, item): """ applies the selected regime to the current target :param item: """ self.apply_regime_by_name(str(item.text())) def apply_regime_by_name(self, regime_name): """ :param regime_name: :return: """ # get regime idx try: idx = map(itemgetter("Name"), self._regimes).index(regime_name) except ValueError as e: self._logger.info("apply_regime_by_name(): Error no regime called {0}".format(regime_name)) return # apply self._apply_regime_by_idx(idx) def _apply_regime_by_idx(self, index=0): if index >= len(self._regimes): self._logger.error("applyRegime: index error! ({})".format(index)) return reg_name = self._regimes[index]["Name"] self.statusBar().showMessage("regime {} applied.".format(reg_name), 1000) self._logger.info("applying regime '{}'".format(reg_name)) self._current_regime_index = index self.sim.set_regime(self._regimes[index]) def execute_regimes_clicked(self): """ execute all regimes in the current list """ self.runningBatch = True self._current_regime_index = -1 self.regimeFinished.emit() def run_next_regime(self): """ executes the next regime """ # are we finished? if self._current_regime_index == len(self._regimes)-1: self.finishedRegimeBatch.emit() return self._apply_regime_by_idx(self._current_regime_index + 1) self.start_simulation() def regime_batch_finished(self): self.runningBatch = False self.actExecuteRegimes.setDisabled(False) # self._current_regime_index = 0 self.statusLabel.setText("All regimes have been simulated!") self.actSave.setDisabled(True) def simulation_finished(self, data): """ main hook to be called by the simulation interface if integration is finished integration finished, enable play button and update plots :param data: dict with simulation data """ self._logger.info(u"simulation finished") self.statusLabel.setText(u"simulation finished.") self.actSimulate.setDisabled(False) self.shortRunSimulation.setEnabled(True) self.shortRunRegimeBatch.setEnabled(True) self.actPlayPause.setDisabled(False) self.shortPlayPause.setEnabled(True) self.shortSaveResult.setEnabled(True) self.actStop.setDisabled(False) self.actSave.setDisabled(False) self.speedDial.setDisabled(False) self.timeSlider.setDisabled(False) self.sim.simulationProgressChanged.disconnect(self.guiProgress.setValue) self.statusBar().removeWidget(self.guiProgress) self.timeSlider.triggerAction(QtGui.QAbstractSlider.SliderToMinimum) self.currentDataset = data self._read_results() self._update_data_list() self._update_plots() self.stop_animation() # self.playAnimation() if self.runningBatch: regime_name = self._regimes[self._current_regime_index]["Name"] self._save_data(regime_name) self.regimeFinished.emit() else: self.actExecuteRegimes.setDisabled(False) def simulation_failed(self, data): """ integration failed, enable play button and update plots :param data: """ self.statusLabel.setText(u"simulation failed!") self.simulation_finished(data) def _read_results(self): self.currentStepSize = 1.0/self.currentDataset["results"]["Simulation"]['measure rate'] self.currentEndTime = self.currentDataset["results"]["Simulation"]["end time"] self.validData = True def add_plot_to_dock(self, plot_widget): self.d3.addWidget(plot_widget) def increment_playback_speed(self): self.speedDial.setValue(self.speedDial.value() + self.speedDial.singleStep()) def decrement_playback_speed(self): self.speedDial.setValue(self.speedDial.value() - self.speedDial.singleStep()) def reset_playback_speed(self): self.speedDial.setValue(self.speedDial.range()/2) def increment_playback_time(self): """ go one time step forward in playback """ increment = self.playbackGain * self.playbackTimeout / 1000 if self.playbackTime + increment <= self.currentEndTime: self.playbackTime += increment pos = self.playbackTime / self.currentEndTime * self.timeSliderRange self.timeSlider.blockSignals(True) self.timeSlider.setValue(pos) self.timeSlider.blockSignals(False) self.playbackTimeChanged.emit() else: self.pause_animation() return def update_playback_gain(self, val): """ adjust playback time to slider value :param val: """ self.playbackGain = 10**(5.0*(val - self.speedDial.maximum()/2)/self.speedDial.maximum()) def update_playback_time(self): """ adjust playback time to slider value """ self.playbackTime = self.timeSlider.value()/self.timeSliderRange*self.currentEndTime self.playbackTimeChanged.emit() return def update_gui(self): """ updates the graphical user interface, including: - timestamp - visualisation - time cursor in diagrams """ if not self.validData: return self.timeLabel.setText(u"current time: %4f" % self.playbackTime) # update time cursor in plots self._update_time_cursor() # update state of rendering if self.visualizer: state = self.interpolate(self.currentDataset["results"]["Solver"]) self.visualizer.update_scene(state) self.vtkWidget.GetRenderWindow().Render() def interpolate(self, data): """ find corresponding item in data-set that fits the current playback time :param data: data-set in which the correct datum has to be found :return: datum fitting the current playback time """ idx = next((index for index, val in enumerate(self.currentDataset["results"]["time"]) if val >= self.playbackTime), None) if idx is None: self._logger.info(u"interpolate(): Error no entry found for t={0}".format(self.playbackTime)) return None else: if len(data.shape) == 1: return data[idx] elif len(data.shape) == 2: return data[idx, :] else: self._logger.info(u"interpolate(): Error Dimension {0} not understood.".format(data.shape)) return None def _update_data_list(self): self.dataList.clear() for module, results in self.currentDataset["results"].iteritems(): if not isinstance(results, np.ndarray): continue if len(results.shape) == 1: self.dataList.insertItem(0, "{0}".format(module)) elif len(results.shape) == 2: for col in range(results.shape[1]): self.dataList.insertItem(0, "{0}.{1}".format(module, col)) elif len(results.shape) == 3: for col in range(results.shape[1]): self.dataList.insertItem(0, "{0}.{1}".format(module, col)) def create_plot(self, item): """ creates a plot widget corresponding to the ListItem :param item: ListItem """ title = str(item.text()) data = self._get_data_by_name(title) t = self.currentDataset["results"]["time"] dock = pg.dockarea.Dock(title) self.area.addDock(dock, "above", self.plotDocks[-1]) widget = pg.PlotWidget(title=title) widget.plot(x=t, y=data) time_line = pg.InfiniteLine(self.playbackTime, angle=90, movable=False, pen=pg.mkPen("#FF0000", width=2.0)) widget.getPlotItem().addItem(time_line) # enable grid widget.showGrid(True, True) dock.addWidget(widget) self.plotDocks.append(dock) self.plotWidgets.append(widget) self.timeLines.append(time_line) def _get_data_by_name(self, name): tmp = name.split(".") if len(tmp) == 1: data = self.currentDataset["results"][tmp[0]] # if the data-set contains 1d array -> convert to float data = [float(x) for x in data] elif len(tmp) == 2: if len(self.currentDataset["results"][tmp[0]].shape) == 2: data = self.currentDataset["results"][tmp[0]][:, tmp[1]] if len(self.currentDataset["results"][tmp[0]].shape) == 3: # data-set has the structure [ [[1]], [[2]] ] data = self.currentDataset['results'][tmp[0]][..., tmp[1], 0] return data def _update_time_cursor(self): """ updates the time lines of all plot windows """ for line in self.timeLines: line.setValue(self.playbackTime) def _update_plots(self): """ plot the fresh simulation data """ for dock in self.plotDocks: for widget in dock.widgets: if not self.dataList.findItems(dock.name(), QtCore.Qt.MatchExactly): # no data for this plot -> reset it widget.getPlotItem().clear() # TODO remove tab from dock and del instance else: widget.getPlotItem().clear() x_data = self.currentDataset["results"]["time"] y_data = self._get_data_by_name(dock.name()) widget.getPlotItem().plot(x=x_data, y=y_data) def target_view_changed(self, index): self.targetView.resizeColumnToContents(0) def postprocessing_clicked(self): """ starts the post- and metaprocessing application """ self._logger.info(u"launching postprocessor") self.statusBar().showMessage(u"launching postprocessor", 1000) if self.postprocessor is None: self.postprocessor = PostProcessor() self.postprocessor.show() def reset_camera_clicked(self): """ reset camera in vtk window """ self.visualizer.reset_camera() self.vtkWidget.GetRenderWindow().Render()
class PostProcessor(QtGui.QMainWindow): sim_results_changed = pyqtSignal() post_results_changed = pyqtSignal() figures_changed = pyqtSignal(list, str) def __init__(self, parent=None): QtGui.QMainWindow.__init__(self, parent) self._logger = logging.getLogger(self.__class__.__name__) self.setWindowTitle(u"Processing") self.setWindowIcon(QtGui.QIcon(get_resource("processing.png"))) self.mainFrame = QtGui.QWidget(self) self.resize(1000, 600) # toolbar self.toolBar = QtGui.QToolBar(u"file control") self.toolBar.setIconSize(QtCore.QSize(24, 24)) self.addToolBar(self.toolBar) self.actLoad = QtGui.QAction(self) self.actLoad.setText(u"load result file") self.actLoad.setIcon(QtGui.QIcon(get_resource("load.png"))) self.actLoad.setDisabled(False) self.actLoad.triggered.connect(self.load_result_files) self.actPostLoad = QtGui.QAction(self) self.actPostLoad.setText(u"load post-result file") self.actPostLoad.setIcon(QtGui.QIcon(get_resource("load.png"))) self.actPostLoad.setDisabled(False) self.actPostLoad.triggered.connect(self.load_post_result_files) self.actSwitch = QtGui.QAction(self) self.actSwitch.setText(u"switch display mode") self.actSwitch.setIcon(QtGui.QIcon(get_resource("left_mode.png"))) self.actSwitch.setDisabled(False) self.actSwitch.triggered.connect(self.switch_sides) self.displayLeft = True self.spacer1 = QtGui.QWidget() self.spacer2 = QtGui.QWidget() self.spacer1.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) self.spacer2.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) self.actReloadMethods = QtGui.QAction(self) self.actReloadMethods.setText(u"reload methods") self.actReloadMethods.setIcon(QtGui.QIcon(get_resource("reload.png"))) self.actReloadMethods.setDisabled(False) self.actReloadMethods.triggered.connect(self.update_post_method_list) self.actReloadMetaMethods = QtGui.QAction(self) self.actReloadMetaMethods.setText(u"reload meta methods") self.actReloadMetaMethods.setIcon(QtGui.QIcon(get_resource("reload.png"))) self.actReloadMetaMethods.setDisabled(False) self.actReloadMetaMethods.triggered.connect(self.update_meta_method_list) self.toolBar.addAction(self.actLoad) self.toolBar.addAction(self.actReloadMethods) self.toolBar.addWidget(self.spacer1) self.toolBar.addAction(self.actSwitch) self.toolBar.addWidget(self.spacer2) self.toolBar.addAction(self.actReloadMetaMethods) self.toolBar.addAction(self.actPostLoad) # main window self.grid = QtGui.QGridLayout(self.mainFrame) self.grid.setColumnMinimumWidth(0, 70) self.grid.setColumnStretch(0, 0) self.grid.setColumnStretch(1, 1) self.methodList = QtGui.QListWidget(self) self.methodList.itemDoubleClicked.connect(self.post_processor_clicked) self.update_post_method_list() self.metaMethodList = QtGui.QListWidget(self) self.metaMethodList.itemDoubleClicked.connect(self.meta_processor_clicked) self.update_meta_method_list() self.sim_result_list = QtGui.QListWidget(self) self.sim_results_changed.connect(self.update_result_list) self.results = [] self.delShort = QtGui.QShortcut(QtGui.QKeySequence(QtCore.Qt.Key_Delete), self.sim_result_list) self.delShort.activated.connect(self.remove_result_item) # figures self._figure_dict = {} self.figures_changed.connect(self.update_figure_lists) self.post_figure_list = QtGui.QListWidget(self) self.post_figure_list.currentItemChanged.connect(self.current_figure_changed) self.meta_figure_list = QtGui.QListWidget(self) self.meta_figure_list.currentItemChanged.connect(self.current_figure_changed) self.plotView = QtGui.QWidget() self.lastFigure = None self.post_result_list = QtGui.QListWidget(self) self.post_results_changed.connect(self.update_post_result_list) self.post_results = [] self.delShortPost = QtGui.QShortcut(QtGui.QKeySequence(QtCore.Qt.Key_Backspace), self.post_result_list) self.delShortPost.activated.connect(self.remove_post_result_item) # log window self.log_list = QPlainTextEditLogger(self) self.log_list.setLevel(logging.INFO) formatter = logging.Formatter(fmt="%(asctime)s - %(name)s - %(levelname)s - %(message)s", datefmt="%H:%M:%S") self.filter = PostFilter() self.log_list.addFilter(self.filter) self.log_list.setFormatter(formatter) logging.getLogger().addHandler(self.log_list) self.grid.addWidget(QtGui.QLabel(u"result files:"), 0, 0) self.grid.addWidget(self.sim_result_list, 1, 0) self.grid.addWidget(QtGui.QLabel(u"Postprocessors:"), 2, 0) self.grid.addWidget(self.methodList, 3, 0) self.grid.addWidget(QtGui.QLabel(u"figures:"), 4, 0) self.grid.addWidget(self.post_figure_list, 5, 0) self.grid.addWidget(QtGui.QLabel(u"selected figure:"), 0, 1) self.grid.addWidget(QtGui.QLabel(u"postprocessor files:"), 0, 2) self.grid.addWidget(self.post_result_list, 1, 2) self.grid.addWidget(QtGui.QLabel(u"Metaprocessors:"), 2, 2) self.grid.addWidget(self.metaMethodList, 3, 2) self.grid.addWidget(QtGui.QLabel(u"figures:"), 4, 2) self.grid.addWidget(self.meta_figure_list, 5, 2) self.grid.addWidget(self.log_list.widget, 6, 0, 1, 3) self.mainFrame.setLayout(self.grid) self.setCentralWidget(self.mainFrame) # status bar self.statusBar = QtGui.QStatusBar(self) self.setStatusBar(self.statusBar) # self.statusBar.addPermanentWidget(self.log_list.widget) # load test results # filePath = os.path.join(os.pardir, 'results', 'simulation', 'default', 'example.pmr') # self._loadResultFile(filePath) # # load test post-results # filePath = os.path.join(os.pardir, 'results', 'postprocessing', 'default') # self._loadResultFile(filePath) def load_result_files(self): path = os.path.join(os.path.pardir, "results", "simulation") dialog = QtGui.QFileDialog(self) dialog.setFileMode(QtGui.QFileDialog.ExistingFiles) dialog.setDirectory(path) dialog.setNameFilter("PyMoskito Result files (*.pmr)") files = None if dialog.exec_(): files = dialog.selectedFiles() if files: for single_file in files: self._load_result_file(unicode(single_file.toUtf8(), encoding="utf-8")) def _load_result_file(self, file_name): """ loads a result file """ self._logger.info(u"loading result file {}".format(file_name)) with open(file_name.encode("utf-8"), "rb") as f: self.results.append(cPickle.load(f)) self.sim_results_changed.emit() def update_result_list(self): self.sim_result_list.clear() for res in self.results: name = res["regime name"] self.sim_result_list.addItem(name) def remove_result_item(self): if self.sim_result_list.currentRow() >= 0: del self.results[self.sim_result_list.currentRow()] self.sim_result_list.takeItem(self.sim_result_list.currentRow()) def load_post_result_files(self): path = os.path.join(os.path.pardir, "results", "processing") dialog = QtGui.QFileDialog(self) dialog.setFileMode(QtGui.QFileDialog.ExistingFiles) dialog.setDirectory(path) dialog.setNameFilter("Postprocessing Output files (*.pof)") files = None if dialog.exec_(): files = dialog.selectedFiles() if files: for selectedFile in files: self._load_post_result_file(unicode(selectedFile.toUtf8(), encoding="utf-8")) def _load_post_result_file(self, file_name): """ loads a post-result file (.pof) """ name = os.path.split(file_name)[-1][:-4] with open(file_name, "r") as f: results = cPickle.load(f) results.update({"name": name}) self.post_results.append(results) self.post_results_changed.emit() def update_post_result_list(self): self.post_result_list.clear() for res in self.post_results: name = res["name"] self.post_result_list.addItem(name) def remove_post_result_item(self): if self.post_result_list.currentRow() >= 0: del self.post_results[self.post_result_list.currentRow()] self.post_result_list.takeItem(self.post_result_list.currentRow()) def update_post_method_list(self): self.methodList.clear() modules = pm.get_registered_processing_modules(PostProcessingModule) for module in modules: self.methodList.addItem(module[1]) def update_meta_method_list(self): self.metaMethodList.clear() modules = pm.get_registered_processing_modules(MetaProcessingModule) for module in modules: self.metaMethodList.addItem(module[1]) def post_processor_clicked(self, item): self.run_processor(str(item.text()), "post") def meta_processor_clicked(self, item): self.run_processor(str(item.text()), "meta") def run_processor(self, name, processor_type): if processor_type == "post": result_files = self.results base_cls = PostProcessingModule elif processor_type == "meta": result_files = self.post_results base_cls = MetaProcessingModule else: self._logger.error(u"unknown processor type {0}".format(processor_type)) raise ValueError("unknown processor type {0}".format(processor_type)) if not result_files: self._logger.warning("run_processor() Error: no result file loaded!") return processor_cls = pm.get_processing_module_class_by_name(base_cls, name) processor = processor_cls() figs = [] try: self._logger.info("executing processor '{0}'".format(name)) figs = processor.process(self.results) except Exception, err: self._logger.exception("Error in processor") self.figures_changed.emit(figs, processor_type) self._logger.info("finished postprocessing")