def _create_tabs(self): """ Create widgets (graph, grid and table) for the view and return the tabs that contain them. """ # Create the grid. self._grid = Grid(self._settings) self._grid_checkbox = QtGui.QCheckBox("Only show current measurement") self._grid_checkbox.stateChanged.connect(self._grid.toggle) grid_layout = QtGui.QVBoxLayout() grid_layout.addWidget(self._grid_checkbox) grid_layout.addWidget(self._grid) grid_widget = QtGui.QWidget() grid_widget.setLayout(grid_layout) # Create the graph and table. self._graph = Graph(self._settings) self._table = Table(self._settings) # Create the top tabs. top_tabs = QtGui.QTabWidget() top_tabs.addTab(self._canvas, "Image") top_tabs.addTab(grid_widget, "Grid") # Create the bottom tabs. bottom_tabs = QtGui.QTabWidget() bottom_tabs.addTab(self._graph.create(), "Graph") bottom_tabs.addTab(self._table.create(), "Table") return top_tabs, bottom_tabs
class Control_Panel_Reconstruction_View(Control_Panel_View): def __init__(self, controller, settings): super(Control_Panel_Reconstruction_View, self).__init__(controller, settings) self._running = False self._axes = None self._canvas = None self._image = None self._graph = None self._grid = None self._grid_checkbox = None self._table = None self._sources = [ { "title": "Dataset", "component": "reconstruction_dataset", "buffer": Dataset_Buffer }, { "title": "Dump", "component": "reconstruction_dump", "buffer": Dump_Buffer }, { "title": "Stream", "component": "reconstruction_stream", "buffer": Stream_Buffer } ] self._panels = None self._toggle_button = None self._snapshot_button = None self._source_forms = [] self._pause_time = self._settings.get("reconstruction_pause_time") * 1000 self._percentiles = None self._interpolation = None self._chunk_size = None self._cmap = None self._coordinator = None self._buffer = None self._stream_recorder = None self._reconstructor = None self._chunk_count = 0 self._import_manager = Import_Manager() self._stacked_reconstructor = None self._stacked_model = None def show(self): """ Show the reconstruction view. """ self._add_menu_bar() # Create the image. figure = plt.figure(frameon=False) self._axes = figure.add_axes([0, 0, 1, 1]) self._axes.axis("off") self._canvas = FigureCanvas(figure) # Create the tabs (and corresponding widgets). top_tabs, bottom_tabs = self._create_tabs() # Create the panels. These are tabs containing the forms for each input # source (dataset, dump and stream). Additionally, there are stacked # widgets for the reconstructor-specific and model-specific settings. self._panels = QtGui.QTabWidget() self._panels.setSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Minimum) self._source_forms = [] arguments = self._controller.arguments self._stacked_reconstructor = Stacked_Settings_Form(arguments, "reconstructor_class") self._stacked_model = Stacked_Settings_Form(arguments, "model_class") for source in self._sources: form = SettingsTableWidget(arguments, source["component"]) # Handle changes to the reconstructor and model class combo box # selection widgets to show its settings in the respective stacked # widget. self._stacked_reconstructor.register_form(form) self._stacked_model.register_form(form) # Register the source settings widget. self._panels.addTab(form, source["title"]) self._source_forms.append(form) # Create the toggle button (using the stopped state as default). self._toggle_button = QtGui.QPushButton(QtGui.QIcon("assets/start.png"), "Start") self._toggle_button.clicked.connect(self._toggle) # Create the snapshot button. self._snapshot_button = QtGui.QPushButton(QtGui.QIcon("assets/snapshot.png"), "Snapshot") self._snapshot_button.clicked.connect(self._snapshot) # Create the layout and add the widgets. vbox_left_buttons = QtGui.QHBoxLayout() vbox_left_buttons.addWidget(self._toggle_button) vbox_left_buttons.addWidget(self._snapshot_button) vbox_left = QtGui.QVBoxLayout() vbox_left.addWidget(self._panels) vbox_left.addWidget(self._stacked_reconstructor) vbox_left.addWidget(self._stacked_model) vbox_left.addLayout(vbox_left_buttons) vbox_right = QtGui.QVBoxLayout() vbox_right.addWidget(top_tabs, 2) vbox_right.addWidget(bottom_tabs, 1) hbox = QtGui.QHBoxLayout(self._controller.central_widget) hbox.addLayout(vbox_left) hbox.addLayout(vbox_right) # Update the stacked widgets when switching tabs in the panel, and # ensure the first stacked widget is loaded. self._panels.currentChanged.connect(self._update_form) self._update_form(0) def _create_tabs(self): """ Create widgets (graph, grid and table) for the view and return the tabs that contain them. """ # Create the grid. self._grid = Grid(self._settings) self._grid_checkbox = QtGui.QCheckBox("Only show current measurement") self._grid_checkbox.stateChanged.connect(self._grid.toggle) grid_layout = QtGui.QVBoxLayout() grid_layout.addWidget(self._grid_checkbox) grid_layout.addWidget(self._grid) grid_widget = QtGui.QWidget() grid_widget.setLayout(grid_layout) # Create the graph and table. self._graph = Graph(self._settings) self._table = Table(self._settings) # Create the top tabs. top_tabs = QtGui.QTabWidget() top_tabs.addTab(self._canvas, "Image") top_tabs.addTab(grid_widget, "Grid") # Create the bottom tabs. bottom_tabs = QtGui.QTabWidget() bottom_tabs.addTab(self._graph.create(), "Graph") bottom_tabs.addTab(self._table.create(), "Table") return top_tabs, bottom_tabs def _toggle(self): """ Toggle the state of the reconstruction (start or stop). """ self._running = not self._running if self._running: self._toggle_button.setIcon(QtGui.QIcon("assets/stop.png")) self._toggle_button.setText("Stop") self._start() else: self._toggle_button.setIcon(QtGui.QIcon("assets/start.png")) self._toggle_button.setText("Start") def _snapshot(self): """ Snapshot the current reconstructed image. """ if self._running and self._image is not None: plt.imsave("snapshots/{}.pdf".format(datetime.datetime.now()), self._image, origin="lower") else: message = "Snapshotting is only possible when the reconstruction is started and an image is rendered." QtGui.QMessageBox.critical(self._controller.central_widget, "Snapshot error", message) def _update_form(self, index): """ Update the stacked widget with the reconstructor settings based on the reconstructor combo box in the current source form. """ form = self._source_forms[index] self._stacked_reconstructor.update(form) self._stacked_model.update(form) def _set_form_settings(self, form): """ Retrieve and update settings from a settings form widget. If an error occurs, it is displayed and the method returns `None` instead of the `Settings` object. """ settings = form.get_settings() try: values, disallowed = form.get_all_values() form.check_disallowed(disallowed) except ValueError as e: QtGui.QMessageBox.critical(self._controller.central_widget, "Invalid value", e.message) return None for key, value in values.iteritems(): try: settings.set(key, value) except ValueError as e: QtGui.QMessageBox.critical(self._controller.central_widget, "Settings error", e.message) return None return settings def _start(self): """ Start the reconstruction process. """ # Determine the current panel and update the settings from the form. panel_id = self._panels.currentIndex() source = self._sources[panel_id] source_form = self._source_forms[panel_id] settings = self._set_form_settings(source_form) if settings is None: self._toggle() return # Update reconstructor and model settings, respectively, if ones that # have settings are selected. for stacked_widget in (self._stacked_reconstructor, self._stacked_model): form = stacked_widget.currentWidget() if isinstance(form, SettingsTableWidget): form_settings = self._set_form_settings(form) if form_settings is None: self._toggle() return # Fetch the settings for the reconstruction. self._percentiles = settings.get("percentiles") self._interpolation = settings.get("interpolation") self._chunk_size = settings.get("chunk_size") # Prepare the color map for the reconstruction. cmap = plt.get_cmap(settings.get("cmap")) self._cmap = np.array([cmap(color) for color in range(256)]) * 255 # Create the buffer and reconstructor. try: self._create_buffer(source, settings) self._create_reconstructor(settings) except IOError as e: QtGui.QMessageBox.critical(self._controller.central_widget, "File error", "Could not open file '{}': {}".format(e.filename, e.strerror)) self._toggle() return except StandardError as e: QtGui.QMessageBox.critical(self._controller.central_widget, "Initialization error", e.message) self._toggle() return # Create the coordinator. self._coordinator = Coordinator(self._controller.arguments, self._buffer) # Clear the widgets. self._graph.clear() self._graph.setup(self._buffer) self._table.clear() self._grid.clear() self._grid.setup(self._buffer) # Clear the image. self._axes.cla() self._axes.axis("off") self._canvas.draw() self._image = None # Execute the reconstruction and visualization. self._chunk_count = 0 self._loop() def _create_buffer(self, source, settings): """ Create the buffer for the reconstruction process (depending on the data source). """ buffer_class = source["buffer"] self._buffer = buffer_class(settings) if isinstance(self._buffer, Stream_Buffer): self._buffer.register_rf_sensor(self._controller.rf_sensor) if settings.get("stream_record") or settings.get("stream_calibrate"): # Create a stream recorder instance to record all incoming # packets. The existence of this object is enough to let the # loop handle the recording process. self._stream_recorder = Stream_Recorder(self._controller, settings) def _create_reconstructor(self, settings): """ Create the reconstructor for the reconstruction process. """ reconstructor = settings.get("reconstructor_class") reconstructor_class = self._import_manager.load_class(reconstructor, relative_module="reconstruction") self._reconstructor = reconstructor_class(self._controller.arguments) def _loop(self): """ Prepare the reconstruction loop by handling stopping and repeating. """ # Stop if the stop button has been pressed. if not self._running: if self._stream_recorder is not None: self._stream_recorder.export() self._stream_recorder = None return self._execute() QtCore.QTimer.singleShot(self._pause_time, self._loop) def _execute(self): """ Execute the reconstruction loop by handling incoming packets. """ # If no packets are available yet, wait for them to arrive. if self._buffer.count() == 0: return try: packet, calibrated_rssi = self._buffer.get() except (TypeError, KeyError): # The buffer returned `None` or a calibration value is not # available, so ignore this packet. self._controller.thread_manager.log("control_panel_reconstruction_view") return # Update the widgets with the packet. self._graph.update(packet) self._grid.update(packet) self._table.update(packet) if self._stream_recorder is not None: self._stream_recorder.update(packet) # Only use packets with valid source and destination locations. if not packet.get("from_valid") or not packet.get("to_valid"): return # Skip rendering when we are calibrating to reduce CPU usage and because # the rendered images are not meaningful. panel_id = self._panels.currentIndex() source_form = self._source_forms[panel_id] settings = source_form.get_settings() if isinstance(self._buffer, Stream_Buffer) and settings.get("stream_calibrate"): return # We attempt to reconstruct an image when the coordinator successfully # updated the weight matrix and the RSSI vector and when we have obtained # the required number of measurements to fill a chunk. if self._coordinator.update(packet, calibrated_rssi): self._chunk_count += 1 if self._chunk_count >= self._chunk_size: self._chunk_count = 0 thread.start_new_thread(self._render, ()) def _render(self): """ Render and draw the image using Matplotlib. This runs in a separate thread. """ try: # Get the list of pixel values from the reconstructor. pixels = self._reconstructor.execute(self._coordinator.get_weight_matrix(), self._coordinator.get_rssi_vector(), buffer=self._buffer) # Reshape the list of pixel values to form the image. Smoothen the image # by suppressing pixel values that do not correspond to high attenuation. pixels = pixels.reshape(self._buffer.size) levels = [np.percentile(pixels, self._percentiles[0]), np.percentile(pixels, self._percentiles[1])] image = pg.functions.makeRGBA(pixels, levels=levels, lut=self._cmap)[0] # Ignore empty images. This may happen after applying the levels # when not enough data is present yet. if len(np.unique(image)) == 1: return # Draw the image onto the canvas and apply interpolation. self._axes.axis("off") self._axes.imshow(image, origin="lower", interpolation=self._interpolation) self._canvas.draw() self._image = image # Delete the image from memory now that it is drawn. self._axes.cla() except StandardError: # There is not enough data yet for the reconstruction algorithm. pass
class Control_Panel_Reconstruction_View(Control_Panel_View): def __init__(self, controller, settings): super(Control_Panel_Reconstruction_View, self).__init__(controller, settings) self._running = False self._axes = None self._canvas = None self._image = None self._graph = None self._grid = None self._grid_checkbox = None self._table = None self._sources = [{ "title": "Dataset", "component": "reconstruction_dataset", "buffer": Dataset_Buffer }, { "title": "Dump", "component": "reconstruction_dump", "buffer": Dump_Buffer }, { "title": "Stream", "component": "reconstruction_stream", "buffer": Stream_Buffer }] self._panels = None self._toggle_button = None self._snapshot_button = None self._source_forms = [] self._pause_time = self._settings.get( "reconstruction_pause_time") * 1000 self._percentiles = None self._interpolation = None self._chunk_size = None self._cmap = None self._coordinator = None self._buffer = None self._stream_recorder = None self._reconstructor = None self._chunk_count = 0 self._import_manager = Import_Manager() self._stacked_reconstructor = None self._stacked_model = None def show(self): """ Show the reconstruction view. """ self._add_menu_bar() # Create the image. figure = plt.figure(frameon=False) self._axes = figure.add_axes([0, 0, 1, 1]) self._axes.axis("off") self._canvas = FigureCanvas(figure) # Create the tabs (and corresponding widgets). top_tabs, bottom_tabs = self._create_tabs() # Create the panels. These are tabs containing the forms for each input # source (dataset, dump and stream). Additionally, there are stacked # widgets for the reconstructor-specific and model-specific settings. self._panels = QtGui.QTabWidget() self._panels.setSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Minimum) self._source_forms = [] arguments = self._controller.arguments self._stacked_reconstructor = Stacked_Settings_Form( arguments, "reconstructor_class") self._stacked_model = Stacked_Settings_Form(arguments, "model_class") for source in self._sources: form = SettingsTableWidget(arguments, source["component"]) # Handle changes to the reconstructor and model class combo box # selection widgets to show its settings in the respective stacked # widget. self._stacked_reconstructor.register_form(form) self._stacked_model.register_form(form) # Register the source settings widget. self._panels.addTab(form, source["title"]) self._source_forms.append(form) # Create the toggle button (using the stopped state as default). self._toggle_button = QtGui.QPushButton( QtGui.QIcon("assets/start.png"), "Start") self._toggle_button.clicked.connect(self._toggle) # Create the snapshot button. self._snapshot_button = QtGui.QPushButton( QtGui.QIcon("assets/snapshot.png"), "Snapshot") self._snapshot_button.clicked.connect(self._snapshot) # Create the layout and add the widgets. vbox_left_buttons = QtGui.QHBoxLayout() vbox_left_buttons.addWidget(self._toggle_button) vbox_left_buttons.addWidget(self._snapshot_button) vbox_left = QtGui.QVBoxLayout() vbox_left.addWidget(self._panels) vbox_left.addWidget(self._stacked_reconstructor) vbox_left.addWidget(self._stacked_model) vbox_left.addLayout(vbox_left_buttons) vbox_right = QtGui.QVBoxLayout() vbox_right.addWidget(top_tabs, 2) vbox_right.addWidget(bottom_tabs, 1) hbox = QtGui.QHBoxLayout(self._controller.central_widget) hbox.addLayout(vbox_left) hbox.addLayout(vbox_right) # Update the stacked widgets when switching tabs in the panel, and # ensure the first stacked widget is loaded. self._panels.currentChanged.connect(self._update_form) self._update_form(0) def _create_tabs(self): """ Create widgets (graph, grid and table) for the view and return the tabs that contain them. """ # Create the grid. self._grid = Grid(self._settings) self._grid_checkbox = QtGui.QCheckBox("Only show current measurement") self._grid_checkbox.stateChanged.connect(self._grid.toggle) grid_layout = QtGui.QVBoxLayout() grid_layout.addWidget(self._grid_checkbox) grid_layout.addWidget(self._grid) grid_widget = QtGui.QWidget() grid_widget.setLayout(grid_layout) # Create the graph and table. self._graph = Graph(self._settings) self._table = Table(self._settings) # Create the top tabs. top_tabs = QtGui.QTabWidget() top_tabs.addTab(self._canvas, "Image") top_tabs.addTab(grid_widget, "Grid") # Create the bottom tabs. bottom_tabs = QtGui.QTabWidget() bottom_tabs.addTab(self._graph.create(), "Graph") bottom_tabs.addTab(self._table.create(), "Table") return top_tabs, bottom_tabs def _toggle(self): """ Toggle the state of the reconstruction (start or stop). """ self._running = not self._running if self._running: self._toggle_button.setIcon(QtGui.QIcon("assets/stop.png")) self._toggle_button.setText("Stop") self._start() else: self._toggle_button.setIcon(QtGui.QIcon("assets/start.png")) self._toggle_button.setText("Start") def _snapshot(self): """ Snapshot the current reconstructed image. """ if self._running and self._image is not None: plt.imsave("snapshots/{}.pdf".format(datetime.datetime.now()), self._image, origin="lower") else: message = "Snapshotting is only possible when the reconstruction is started and an image is rendered." QtGui.QMessageBox.critical(self._controller.central_widget, "Snapshot error", message) def _update_form(self, index): """ Update the stacked widget with the reconstructor settings based on the reconstructor combo box in the current source form. """ form = self._source_forms[index] self._stacked_reconstructor.update(form) self._stacked_model.update(form) def _set_form_settings(self, form): """ Retrieve and update settings from a settings form widget. If an error occurs, it is displayed and the method returns `None` instead of the `Settings` object. """ settings = form.get_settings() try: values, disallowed = form.get_all_values() form.check_disallowed(disallowed) except ValueError as e: QtGui.QMessageBox.critical(self._controller.central_widget, "Invalid value", e.message) return None for key, value in values.iteritems(): try: settings.set(key, value) except ValueError as e: QtGui.QMessageBox.critical(self._controller.central_widget, "Settings error", e.message) return None return settings def _start(self): """ Start the reconstruction process. """ # Determine the current panel and update the settings from the form. panel_id = self._panels.currentIndex() source = self._sources[panel_id] source_form = self._source_forms[panel_id] settings = self._set_form_settings(source_form) if settings is None: self._toggle() return # Update reconstructor and model settings, respectively, if ones that # have settings are selected. for stacked_widget in (self._stacked_reconstructor, self._stacked_model): form = stacked_widget.currentWidget() if isinstance(form, SettingsTableWidget): form_settings = self._set_form_settings(form) if form_settings is None: self._toggle() return # Fetch the settings for the reconstruction. self._percentiles = settings.get("percentiles") self._interpolation = settings.get("interpolation") self._chunk_size = settings.get("chunk_size") # Prepare the color map for the reconstruction. cmap = plt.get_cmap(settings.get("cmap")) self._cmap = np.array([cmap(color) for color in range(256)]) * 255 # Create the buffer and reconstructor. try: self._create_buffer(source, settings) self._create_reconstructor(settings) except IOError as e: QtGui.QMessageBox.critical( self._controller.central_widget, "File error", "Could not open file '{}': {}".format(e.filename, e.strerror)) self._toggle() return except StandardError as e: QtGui.QMessageBox.critical(self._controller.central_widget, "Initialization error", e.message) self._toggle() return # Create the coordinator. self._coordinator = Coordinator(self._controller.arguments, self._buffer) # Clear the widgets. self._graph.clear() self._graph.setup(self._buffer) self._table.clear() self._grid.clear() self._grid.setup(self._buffer) # Clear the image. self._axes.cla() self._axes.axis("off") self._canvas.draw() self._image = None # Execute the reconstruction and visualization. self._chunk_count = 0 self._loop() def _create_buffer(self, source, settings): """ Create the buffer for the reconstruction process (depending on the data source). """ buffer_class = source["buffer"] self._buffer = buffer_class(settings) if isinstance(self._buffer, Stream_Buffer): self._buffer.register_rf_sensor(self._controller.rf_sensor) if settings.get("stream_record") or settings.get( "stream_calibrate"): # Create a stream recorder instance to record all incoming # packets. The existence of this object is enough to let the # loop handle the recording process. self._stream_recorder = Stream_Recorder( self._controller, settings) def _create_reconstructor(self, settings): """ Create the reconstructor for the reconstruction process. """ reconstructor = settings.get("reconstructor_class") reconstructor_class = self._import_manager.load_class( reconstructor, relative_module="reconstruction") self._reconstructor = reconstructor_class(self._controller.arguments) def _loop(self): """ Prepare the reconstruction loop by handling stopping and repeating. """ # Stop if the stop button has been pressed. if not self._running: if self._stream_recorder is not None: self._stream_recorder.export() self._stream_recorder = None return self._execute() QtCore.QTimer.singleShot(self._pause_time, self._loop) def _execute(self): """ Execute the reconstruction loop by handling incoming packets. """ # If no packets are available yet, wait for them to arrive. if self._buffer.count() == 0: return try: packet, calibrated_rssi = self._buffer.get() except (TypeError, KeyError): # The buffer returned `None` or a calibration value is not # available, so ignore this packet. self._controller.thread_manager.log( "control_panel_reconstruction_view") return # Update the widgets with the packet. self._graph.update(packet) self._grid.update(packet) self._table.update(packet) if self._stream_recorder is not None: self._stream_recorder.update(packet) # Only use packets with valid source and destination locations. if not packet.get("from_valid") or not packet.get("to_valid"): return # Skip rendering when we are calibrating to reduce CPU usage and because # the rendered images are not meaningful. panel_id = self._panels.currentIndex() source_form = self._source_forms[panel_id] settings = source_form.get_settings() if isinstance(self._buffer, Stream_Buffer) and settings.get("stream_calibrate"): return # We attempt to reconstruct an image when the coordinator successfully # updated the weight matrix and the RSSI vector and when we have obtained # the required number of measurements to fill a chunk. if self._coordinator.update(packet, calibrated_rssi): self._chunk_count += 1 if self._chunk_count >= self._chunk_size: self._chunk_count = 0 thread.start_new_thread(self._render, ()) def _render(self): """ Render and draw the image using Matplotlib. This runs in a separate thread. """ try: # Get the list of pixel values from the reconstructor. pixels = self._reconstructor.execute( self._coordinator.get_weight_matrix(), self._coordinator.get_rssi_vector(), buffer=self._buffer) # Reshape the list of pixel values to form the image. Smoothen the image # by suppressing pixel values that do not correspond to high attenuation. pixels = pixels.reshape(self._buffer.size) levels = [ np.percentile(pixels, self._percentiles[0]), np.percentile(pixels, self._percentiles[1]) ] image = pg.functions.makeRGBA(pixels, levels=levels, lut=self._cmap)[0] # Ignore empty images. This may happen after applying the levels # when not enough data is present yet. if len(np.unique(image)) == 1: return # Draw the image onto the canvas and apply interpolation. self._axes.axis("off") self._axes.imshow(image, origin="lower", interpolation=self._interpolation) self._canvas.draw() self._image = image # Delete the image from memory now that it is drawn. self._axes.cla() except StandardError: # There is not enough data yet for the reconstruction algorithm. pass