.. _understand: Try to understand the code -------------------------- The objects ^^^^^^^^^^^ As experienced before (:ref:`explore`), the program is prepared to read and render vectorial and raster images, surfaces and volumes. These objects are implemented in the files :file:`rimage.py` and :file:`vimage.py`, that you already know from Lab 1 and the files :file:`surface.py` and :file:`volume.py` that we practiced in Session 10 (:ref:`3d_surfaces`) and 11 (:ref:`3d_volumes`). These files are in the zip file you have just downloaded (:ref:`explore`). The scene ^^^^^^^^^ We need an object to keep track of the objects that we read and create. For that, we use the class :py:class:`scene.Scene` implemented in the file :file:`scene.py`. A scene is simply a container of objects: 2D raster images (:py:class:`RImage`), surfaces ( (:py:class:`Surface`) and volumes (:py:class:`Volume`). It also keeps track of the object currently selected and visible (:py:attr:`scene.Scene.current_item`). The main window, the central widget, the plot canvas and the vtk widgets ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Open :file:`image_viewer.py`. It contains the implementation of the class :py:class:`image_viewer.MainWindow` that inherits from :py:class:`QMainWindow` (see QMainWindow_). It is very similar to the what was implemented in the examples (see :ref:`interface_examples`), but now, it has a new attribute called :py:attr:`image_viewer.MainWindow.scene`, an instance of the class :py:class:`scene.Scene`, the object's container. The interface is composed of menus and a central widget which is an instance of the class :py:class:`central.CentralWidget` (:file:`central.py`). The central widget has two attributes: :py:attr:`central.CentralWidget.visuwidget` and :py:attr:`central.CentralWidget.vtkwidget`. The former one is an instance of the class :py:class:`plot_canvas.PlotCanvas` that inherits from the widget class :py:class:`FigureCanvasQTAgg` (see matplolibbackends_). It is a widget that bridges matplotlib and PyQT and allows to render in PyQt graphical areas using matplotlib. We use this widegt to render images. The latter attribute is an instance of class :py:class:`vtkwidget.VTKwidget` (:file:`vtkwidget.py`) that inherits from the class :py:class:`QVTKRenderWindowInteractor` and will provide the same functionaly as the class :py:class:`Rendering` that we used in session 10, but embedded within a PyQt application. This double widget structure is illustrated below. Of course, we could have visualized he images in the vtkWidget as well and have only one widget for rendering. We have done it this way to provide more continuity with Lab 1. .. image:: doblewidget.png :align: center :height: 500 Reading a file ^^^^^^^^^^^^^^ In the *File* menu you will be able to read images (vectorial and raster), surfaces and volumes. You can also from the program as in the examples (:ref:`interface_examples`). When reading an object a file selector widget is shown. This is a pre-defined widget QFileDialog_ provided by PyQt. The action associated to the reading actions invoke the reading method of the corresponding classes. For instance, observe that in the class :py:class:`surface.Surface`, we have implemented the method to read a surface from an stl file. If you'd like to support other types of files, you just need to add the correspondent method in the class Surface and invoke it if the filename has the given extension (the same for images and volumes). How are the newly created image titles added to this selection list? With which names? Investigate the method :py:meth:`image_viewer.MainWindow.add_item_interface` of class :py:class:`image_viewer.MainWindow`.:: def add_item_interface(self, name, cat): itemAction = QAction(name, self) itemAction.triggered.connect(self.select_action) if cat == 'RImage': self.menuImageSelection.addAction(itemAction) elif cat == 'Surface': self.menuSurfaceSelection.addAction(itemAction) elif cat == 'Volume': self.menuVolumeSelection.addAction(itemAction) self.render_current_item() As you can see, when an item of a given name is added to scene, its name is automatically added to the corresponding selection menu, either that of images, surfaces or volumes, depending on the type (class) of the item. Adding a new method and a new dialog ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ In the `Images`, `Surfaces` and `Volumes` menus, there is an option to create objects of each kind procedurally. How would you do to add, for instance, a new method for creating an image with the fantasy method you implemented in :ref:`lab_1` (e.g. concentric circles, stripes, variations of hues ...). In the the method :py:meth:`create_image_menu`, check how the `uniformAction` is created :: uniformAction = QAction(QIcon("resources/icons/unif.png"), "Uniform", self) uniformAction.triggered.connect(self.uniform_image) self.menuImage.addAction(uniformAction) Whenever the user clicks on the option labelled with the text `Uniform`, the method :py:meth:`MainWindow.uniform_image` will be invoked. To add a new option, just copy and paste these 3 lines and adapt them. For instance:: mynewAction = QAction(QIcon("resources/icons/unif.png"), "Fantasy", self) mynewAction.triggered.connect(self.mynew_image) self.menuImage.addAction(mynewAction) Now, check the method :py:meth:`MainWindow.uniform_image`:: def uniform_image(self): ok, width, height, color = self.get_basic_params() if ok: im = RImage("uniform", width, height, color) self.scene.add_item(im) self.add_item_interface(im.name, 'RImage') First, the method :py:meth:`MainWindow.get_basic_params` is invoked to collect the parameters needed to create a uniform image. With these parameters, a new image is created, added to the scene and the name is added to the interface. You have to do the same with your new method (the same for all new methods of creation of objects). Now, analyze the method :py:meth:`MainWindow.get_basic_params`. It opens a dialog and retrieves from it the parameters, casting the string values to their actual type. The dialog is a instance of a class called :py:class:`paramdialog.ParamImageDialog`. Analyze it. Would you know how to add a new option? You'll have to create a new class for each different widget you need. Menu Help ^^^^^^^^^ Check how the option help of the menu opens a pdf file: it simply uses the module subprocess with the command xdg-open. You may need to adapt this for your convenience if you are not linux, but in the final version, leave it as it is, because the lab work will be assessed in Ubuntu. .. toctree:: :glob: .. _PyQt5: https://pypi.org/project/PyQt5 .. _QApplication: https://doc.qt.io/qtforpython/PySide2/QtWidgets/QApplication.html .. _QMainWindow: https://doc.qt.io/qtforpython/PySide2/QtWidgets/QMainWindow.html .. _QAction: https://doc.qt.io/qtforpython/PySide2/QtWidgets/QAction.html .. _QMessageBox: https://doc.qt.io/qtforpython/PySide2/QtWidgets/QMessageBox.html .. _QFileDialog: https://doc.qt.io/qtforpython/PySide2/QtWidgets/QFileDialog.html .. _matplolibbackends: https://matplotlib.org/2.0.0/api/backend_qt5agg_api.html .. _QFileDialog: https://doc.qt.io/qtforpython/PySide2/QtWidgets/QFileDialog.html .. _QMenuBar: https://doc.qt.io/qtforpython/PySide2/QtWidgets/QMenuBar.html .. _horse: https://scikit-image.org/docs/dev/api/skimage.data.html#skimage.data.horse