Source code for mdt.gui.maps_visualizer.renderers.matplotlib_renderer

import logging
import matplotlib
import numpy as np
import copy
from mdt.gui.maps_visualizer.actions import SetZoom, SetAnnotations

matplotlib.use('Qt5Agg')

from PyQt5.QtCore import QTimer

from mdt.visualization.maps.matplotlib_renderer import MapsVisualizer
from matplotlib.figure import Figure
from PyQt5 import QtWidgets, QtCore
from PyQt5.QtCore import pyqtSlot
from PyQt5.QtWidgets import QWidget, QVBoxLayout
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas

from mdt.gui.maps_visualizer.base import DataConfigModel
from mdt.gui.maps_visualizer.renderers.base import PlottingFrame
from mdt.visualization.maps.base import Zoom, VoxelAnnotation


[docs]class MatplotlibPlotting(PlottingFrame, QWidget): def __init__(self, controller, parent=None, plotting_info_viewer=None): super().__init__(controller, plotting_info_viewer=plotting_info_viewer) self._controller.model_updated.connect(self.update_model) self._auto_render = True current_model = self._controller.get_model() self.figure = Figure(facecolor='#bfbfbf') self.visualizer = MapsVisualizer(current_model.get_data(), self.figure) self._axes_data = self.visualizer.render(current_model.get_config()) self.canvas = FigureCanvas(self.figure) self.canvas.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) self.canvas.setFocusPolicy(QtCore.Qt.ClickFocus) self.canvas.setFocus() self.canvas.updateGeometry() layout = QVBoxLayout() layout.setSpacing(0) layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self.canvas) self.setLayout(layout) self.setParent(parent) self.setFocusPolicy(QtCore.Qt.StrongFocus) self.setFocus() self._redraw_timer = QTimer() self._redraw_timer.timeout.connect(self._timer_event) self._redraw_timer.timeout.connect(self._redraw_timer.stop) self._mouse_interaction = _MouseInteraction(self.figure, self._plotting_info_viewer, self._controller) self._mouse_interaction.update_axes_data(self._axes_data) self._previous_model = None self.setMinimumWidth(100)
[docs] def export_image(self, filename, width, height, dpi=100): current_model = self._controller.get_model() width_inch = width / dpi height_inch = height / dpi figure = Figure(figsize=(width_inch, height_inch), dpi=dpi) visualizer = MapsVisualizer(current_model.get_data(), figure) FigureCanvas(figure) visualizer.to_file(filename, current_model.get_config(), dpi=dpi)
[docs] def set_auto_rendering(self, auto_render): self._auto_render = auto_render
[docs] def redraw(self): self._redraw()
@pyqtSlot() def _timer_event(self): self._redraw()
[docs] @pyqtSlot(DataConfigModel) def update_model(self, model): def update(): self._previous_model = model self.visualizer = MapsVisualizer(model.get_data(), self.figure) if self._auto_render: self._redraw_timer.start(300) if not self._previous_model: update() elif model.get_config().visible_changes(self._previous_model.get_config()): update() elif model.get_data() != self._previous_model.get_data(): update()
def _redraw(self): current_model = self._controller.get_model() self.figure.clf() self._axes_data = self.visualizer.render(current_model.get_config()) self._mouse_interaction.update_axes_data(self._axes_data) try: self.figure.canvas.draw() except ValueError as exception: logger = logging.getLogger(__name__) logger.error(exception)
class _MouseInteraction: def __init__(self, figure, plotting_info_viewer, controller): self.figure = figure self.plotting_info_viewer = plotting_info_viewer self.controller = controller self._axes_data = [] self.figure.canvas.mpl_connect('button_press_event', self._button_pressed) self.figure.canvas.mpl_connect('button_release_event', self._button_released) self.figure.canvas.mpl_connect('motion_notify_event', self._mouse_motion) self.figure.canvas.mpl_connect('scroll_event', self._scroll_event) self.figure.canvas.mpl_connect('key_press_event', self._on_key_press) self.figure.canvas.mpl_connect('key_release_event', self._on_key_release) self._in_drag = False self._control_is_held = False self._scrolling_manager = _ScrollingManager(controller) self._dragging_manager = _DraggingManager(controller) def update_axes_data(self, axes_data): """Set the updated axes data. Needs to be called if the axes are updated. Args: axes_data (list of AxisData): the information about the axes """ self._axes_data = axes_data def _button_pressed(self, event): self._dragging_manager.set_starting_point(event.xdata, event.ydata) def _button_released(self, event): if self._in_drag: self._in_drag = False return current_annotations = copy.copy(self.controller.get_model().get_config().annotations) def voxel_index_in_locations(voxel_index): for annotation in current_annotations: if annotation.voxel_index == voxel_index: return True return False def get_existing_matching_annotation_index(voxel_index): for ind, annotation in enumerate(current_annotations): if annotation.voxel_index == voxel_index: return ind raise ValueError('Matching annotation not found') if event.button == 1: axis_data = self._get_matching_axis_data(event.inaxes) if axis_data: x, y = int(np.round(event.xdata)), int(np.round(event.ydata)) index = axis_data.coordinates_to_index(x, y)[:3] if voxel_index_in_locations(index): del current_annotations[get_existing_matching_annotation_index(index)] elif self._control_is_held: current_annotations.append(VoxelAnnotation(index)) else: if len(current_annotations): current_annotations.pop() current_annotations.append(VoxelAnnotation(index)) self.controller.apply_action(SetAnnotations(current_annotations)) else: if self._control_is_held: self.controller.apply_action(SetAnnotations([])) else: if len(current_annotations): current_annotations.pop() self.controller.apply_action(SetAnnotations(current_annotations)) def _scroll_event(self, event): if event.inaxes: if event.button == 'up': self._scrolling_manager.add_up_scroll() else: self._scrolling_manager.add_down_scroll() def _mouse_motion(self, event): if event.button == 1: self._in_drag = True self._drag_images(event) else: self._update_info_box(event) def _on_key_press(self, event): if event.key == 'control': self._control_is_held = True def _on_key_release(self, event): if event.key == 'control': self._control_is_held = False def _drag_images(self, event): self._dragging_manager.mouse_moved(event.xdata, event.ydata) def _update_info_box(self, event): """Update the info box in the plotting info object. Args: event (MouseEvent): a matplotlib mouse event used to update the plotting info """ axis_data = self._get_matching_axis_data(event.inaxes) if axis_data: x, y = int(np.round(event.xdata)), int(np.round(event.ydata)) index = axis_data.coordinates_to_index(x, y) self.plotting_info_viewer.set_voxel_info(axis_data.map_name, (x, y), tuple(index)) else: self.plotting_info_viewer.clear_voxel_info() def _get_matching_axis_data(self, axis): """Get the axis data matching the given axis. Args: axis: the matplotlib axis to match Returns: AxisData: our data container for that axis """ if axis: for axes_data in self._axes_data: if axes_data.axis == axis: return axes_data return None class _DraggingManager: def __init__(self, controller): self.controller = controller self._start_x = 0 self._start_y = 0 self._end_x = 0 self._end_y = 0 self._drag_timer = QTimer() self._drag_timer.timeout.connect(self._perform_drag) self._drag_timer.timeout.connect(self._drag_timer.stop) def set_starting_point(self, x, y): self._start_x = x self._start_y = y def mouse_moved(self, x, y): if x is not None and y is not None: self._end_x = x self._end_y = y self._drag_timer.start(50) def _perform_drag(self): current_model = self.controller.get_model() delta_x = int(np.round(self._start_x) - np.round(self._end_x)) delta_y = int(np.round(self._start_y) - np.round(self._end_y)) config = current_model.get_config() data_info = current_model.get_data() current_zoom = current_model.get_config().zoom max_y = data_info.get_max_y_index(config.dimension, rotate=config.rotate, map_names=config.maps_to_show) max_x = data_info.get_max_x_index(config.dimension, rotate=config.rotate, map_names=config.maps_to_show) new_x0 = current_zoom.p0.x + delta_x new_x1 = (current_zoom.p1.x or max_x) + delta_x new_y0 = current_zoom.p0.y + delta_y new_y1 = (current_zoom.p1.y or max_y) + delta_y if new_x0 < 0: new_x1 -= new_x0 new_x0 = 0 if new_x1 > max_x: new_x0 -= new_x1 new_x1 = max_x if new_y0 < 0: new_y1 -= new_y0 new_y0 = 0 if new_y1 > max_y: new_y0 -= new_y1 new_y1 = max_y try: new_zoom = Zoom.from_coords(new_x0, new_y0, new_x1, new_y1) except ValueError: new_zoom = current_zoom self.controller.apply_action(SetZoom(new_zoom)) self._start_x = self._end_x self._start_y = self._end_y class _ScrollingManager: def __init__(self, controller): self.controller = controller self._scrolls = 0 self._scroll_timer = QTimer() self._scroll_timer.timeout.connect(self._perform_scroll) self._scroll_timer.timeout.connect(self._scroll_timer.stop) def add_up_scroll(self): self._scrolls += 1 self._scroll_timer.start(200) def add_down_scroll(self): self._scrolls -= 1 self._scroll_timer.start(200) def _perform_scroll(self): current_model = self.controller.get_model() config = current_model.get_config() data_info = current_model.get_data() current_zoom = config.zoom max_y = data_info.get_max_y_index(config.dimension, rotate=config.rotate, map_names=config.maps_to_show) max_x = data_info.get_max_x_index(config.dimension, rotate=config.rotate, map_names=config.maps_to_show) scrolling = self._scrolls if abs(self._scrolls) > 1: scrolling *= 3 new_x0 = current_zoom.p0.x + scrolling if new_x0 < 0: new_x0 = 0 new_y0 = current_zoom.p0.y + scrolling if new_y0 < 0: new_y0 = 0 new_x1 = (current_zoom.p1.x or max_x) - scrolling if new_x1 > max_x: new_x1 = max_x new_y1 = (current_zoom.p1.y or max_y) - scrolling if new_y1 > max_y: new_y1 = max_y try: new_zoom = Zoom.from_coords(new_x0, new_y0, new_x1, new_y1) except ValueError: new_zoom = current_zoom self.controller.apply_action(SetZoom(new_zoom)) self._scrolls = 0