The ApplicationController ========================= Interactions between Python and an application, like the ImFusionSuite, can be facilitated by :class:`~imfusion.gui.ApplicationController`, which, among other things, manages loaded data, can visualize data, and records executed algorithms. If you are using the embedded Python interpreter of the ImFusionSuite, an :class:`~imfusion.gui.ApplicationController` is already instantiated. Otherwise, we create a :class:`~imfusion.gui.ConsoleController` which is a special controller that does not provide or require a GUI: .. doctest:: :skipif: no_controller >>> if not imfusion.app: ... imfusion.app = imfusion.gui.ConsoleController() All the previously introduced functions are independent of the :class:`~imfusion.gui.ApplicationController` but some can also be called on the :class:`~imfusion.gui.ApplicationController` instance with some additional side effects: .. doctest:: :skipif: no_controller >>> imfusion.app.open('ct_image.png') [imfusion.SharedImageSet(size: 1, [imfusion.SharedImage(USHORT width: 512 height: 512 spacing: 0.661813x0.661813x1 mm)])] If you executed the previous code in the ImFusionSuite, you might have noted that the image is directly displayed in the main view area. This is because the data gets automatically added to the :class:`~imfusion.gui.DataModel`. This is *not* the case if you load data with :func:`~imfusion.io.load`. .. note:: When writing simple scripts, most likely the added complexity of the :class:`~imfusion.gui.ApplicationController` is not needed, since many functions don't require it (see :func:`~imfusion.algorithm.execute` for example). .. note:: When writing custom algorithms, you should not rely on :data:`~imfusion.app` being available, because :data:`~imfusion.app` could be `None` when a custom algorithm is run outside of the ImFusionSuite. If, on the other hand, you are using Python in combination with the ImFusionSuite application, the :class:`~imfusion.gui.ApplicationController` interface for algorithms can become much more convenient. .. _application.workspaces: Workspaces (.iws files) ----------------------- Workspaces can be loaded and saved with the functions :meth:`~imfusion.gui.ApplicationController.load_workspace` and :meth:`~imfusion.gui.ApplicationController.save_workspace` respectively. Please, check the `ImFusionSuite documentation `_ for more details about workspaces. Interacting with the Display ---------------------------- .. warning:: Displays and annotations are currently only available when using the :mod:`imfusion` module inside the ImFusion Suite. The :class:`~imfusion.gui.Display` is the central widget in the ImFusion Suite that visualizes the loaded datasets. It consists of several :class:`~imfusion.gui.View` that show the datasets in different perspectives or in different ways. The :class:`~imfusion.gui.Display` instance can be accessed through the :attr:`~imfusion.gui.ApplicationController.display`. The following example makes all views visible, switches to a different layout, and shows the 3D view as the prominent view. .. doctest:: :skipif: no_controller >>> display = imfusion.app.display >>> for view in display.views(): ... view.visible = True >>> display.layout_mode = imfusion.gui.LayoutMode.LAYOUT_FOCUS_PLUS_STACK >>> display.focus_view = display.views3d()[0] DataModel --------- The :class:`~imfusion.gui.DataModel` stores all data that is managed by the :class:`~imfusion.gui.ApplicationController` and you can retrieve, add or delete data from it: .. doctest:: :skipif: no_controller >>> dm = imfusion.app.data_model >>> len(dm) 1 >>> img = dm[0] >>> img imfusion.SharedImageSet(size: 1, [imfusion.SharedImage(USHORT width: 512 height: 512 spacing: 0.661813x0.661813x1 mm)]) When adding data created in Python to the :class:`~imfusion.gui.DataModel` it will get copied. The reference to the copy is returned: .. doctest:: :skipif: no_controller >>> new_img = imfusion.SharedImageSet(np.zeros([1, 8, 8, 8, 1], dtype=np.uint8)) >>> new_img = dm.add(new_img) >>> dm.remove(new_img) Running algorithms ------------------ We can also execute algorithms directly on the :class:`~imfusion.gui.ApplicationController`. Any output data will then be added automatically to the :class:`~imfusion.gui.DataModel`: .. doctest:: :skipif: no_controller >>> imfusion.app.execute_algorithm('Base.MorphologicalOperations', [img]) [imfusion.SharedImageSet(size: 1, [imfusion.SharedImage(USHORT width: 512 height: 512 spacing: 0.661813x0.661813x1 mm)])] >>> len(dm) 2 The :class:`~imfusion.gui.ApplicationController` will also keep track of all executed algorithms. You can save the algorithm history in the ImFusionSuite as a workspace (.iws), see also :ref:`application.workspaces`, and replay the algorithms with the same settings. Instead of executing the algorithm right away, we can also just open it: .. doctest:: :skipif: no_controller >>> imfusion.app.add_algorithm('Base.MorphologicalOperations', [img]) This returns a reference to the algorithm instance and will open the corresponding UI element (which we call "Algorithm Controller") in the ImFusionSuite. AnnotationModel --------------- The :class:`~imfusion.gui.Display` enables adding annotations such as lines or circles to a dataset. This functionality is facilitated by the :class:`~imfusion.gui.AnnotationModel`, which holds all annotations similar to how the :class:`~imfusion.gui.DataModel` manages data. Unlike :class:`~imfusion.Data`, :class:`~imfusion.gui.Annotation` instances can only be created through a factory method, :class:`~imfusion.gui.AnnotationModel.create_annotation`. To create a line :class:`~imfusion.gui.Annotation`: .. doctest:: :skipif: no_controller >>> am = imfusion.app.annotation_model >>> annotation = am.create_annotation(imfusion.gui.Annotation.AnnotationType.LINE) >>> annotation.color = (1.0, 0.0, 0.0) # make it red >>> annotation.points = [(0.0, 0.0, 0.0), (100.0, 100.0, 0.0)] The last command assigns the line's points in world coordinates. Each type of :class:`~imfusion.gui.Annotation` requires a specific number of points, which can be queried via the :attr:`~imfusion.gui.Annotation.max_points` property. The Python interface currently provides the following types: * :class:`~imfusion.gui.Annotation.AnnotationType.BOX` * :class:`~imfusion.gui.Annotation.AnnotationType.CIRCLE` * :class:`~imfusion.gui.Annotation.AnnotationType.LINE` * :class:`~imfusion.gui.Annotation.AnnotationType.POINT` * :class:`~imfusion.gui.Annotation.AnnotationType.POLY_LINE` * :class:`~imfusion.gui.Annotation.AnnotationType.RECTANGLE` It is also possible to set the points of an annotation interactively on the display: .. doctest:: :skipif: no_controller >>> annotation = am.create_annotation(imfusion.gui.Annotation.AnnotationType.LINE) >>> annotation.start_editing() You can register a callback which will be called once the user finishes editing the annotation (i.e., when it's completely defined): .. doctest:: :skipif: no_controller >>> def callback(): ... print("All points are defined") >>> a1 = am.create_annotation(imfusion.gui.Annotation.AnnotationType.LINE) >>> a1.on_editing_finished(callback) >>> a1.start_editing() Ideally, you would add the callback before calling :meth:`~imfusion.gui.Annotation.start_editing`. Alternatively, you can also listen for all changes to :attr:`~imfusion.gui.Annotation.points`: .. doctest:: :skipif: no_controller >>> def callback(): ... print("Points changed") >>> a2 = am.create_annotation(imfusion.gui.Annotation.AnnotationType.LINE) >>> a2.on_points_changed(callback) >>> a2.start_editing() Annotations can be queried through the :attr:`~imfusion.gui.AnnotationModel.annotations` attribute: .. doctest:: :skipif: no_controller >>> all_annotations = am.annotations()