{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "\n# Build a custom simplified viewer based on Anatomist\n\nAn even simplified version of the anasimpleviewer application, which may also\nbe used as a programming example. Its code is in the \u201cbin/\u201d\u201d directory of the\nbinary packages.\n\n.. .. figure:: ../images/anaevensimplerviewer.png\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "import anatomist.direct.api as ana\nfrom soma import aims\nfrom soma.aims import colormaphints\nimport sys\nimport os\nfrom soma.qt_gui import qt_backend\nfrom soma.qt_gui.qt_backend import Qt, loadUi\nimport six\nfrom six.moves import zip\n\n\ndef findChild(x, y): return Qt.QObject.findChild(x, Qt.QObject, y)\n\n\nclose_qt = False\nif 'sphinx_gallery' in sys.modules \\\n        or ('IPython' in sys.modules\n            and Qt.QApplication.instance() is None):\n    run_qt = False\n    close_qt = True\nelif Qt.QApplication.instance() is None:\n    run_qt = True\nelse:\n    run_qt = False\n\n\n# start the Anatomist engine, in batch mode (no main window)\na = ana.Anatomist('-b')\n\n# load the anasimpleviewer GUI\nuifile = 'anasimpleviewer-qt4.ui'\nanasimpleviewerdir = os.path.join(six.text_type(a.anatomistSharedPath()),\n                                  'anasimpleviewer')\ncwd = os.getcwd()\n# PyQt4 uic doesn' seem to allow specifying the directory when looking for\n# icon files: we have no other choice than globally changing the working\n# directory\nos.chdir(anasimpleviewerdir)\nawin = loadUi(os.path.join(anasimpleviewerdir, uifile))\nos.chdir(cwd)\n\n# global variables: lists of windows, objects, a fusion2d with a number of\n# volumes in it\nawindows = []\naobjects = []\nfusion2d = []\n\n# vieww: parent block widget for anatomist windows\nvieww = findChild(awin, 'windows')\nviewgridlay = Qt.QGridLayout(vieww)\nnviews = 0\n\n\n# This class holds methods for menu/actions callbacks, and utility functions\n# like load/view objects, remove/delete, etc.\nclass AnaSimpleViewer(Qt.QObject):\n\n    def __init__(self):\n        Qt.QObject.__init__(self)\n        self.filedialogdir = '.'\n\n    def createWindow(self, wintype='Axial'):\n        '''Opens a new window in the windows grid layout.\n        The new window will be set in MNI referential, and have no\n        menu/toolbars.\n        '''\n        global vieww, nviews\n        x = nviews % 2\n        y = nviews // 2\n        nviews += 1\n\n        w = a.createWindow(wintype, no_decoration=True, options={'hidden': 1})\n        w.setAcceptDrops(False)\n        viewgridlay.addWidget(w.getInternalRep(), x, y)\n\n        # keep it in anasimpleviewer list of windows\n        awindows.append(w)\n        a.assignReferential(a.mniTemplateRef, w)\n        # force redrawing in MNI orientation\n        # (there should be a better way to do so...)\n        if wintype == 'Axial':\n            w.muteAxial()\n        elif wintype == 'Coronal':\n            w.muteCoronal()\n        elif wintype == 'Sagittal':\n            w.muteSagittal()\n        elif wintype == 'Oblique':\n            w.muteOblique()\n        # set a black background\n        a.execute('WindowConfig', windows=[w],\n                  light={'background': [0., 0., 0., 1.]})\n\n    def loadObject(self, fname):\n        '''Load an object and display it in all anasimpleviewer windows\n        '''\n        obj = a.loadObject(fname)\n        self.registerObject(obj)\n\n    def registerObject(self, obj):\n        '''Register an object in anasimpleviewer objects list, and display it\n        '''\n        findChild(awin, 'objectslist').addItem(obj.name)\n        # keep it in the global list\n        aobjects.append(obj)\n        if obj.objectType == 'VOLUME':\n            # volume are checked for possible adequate colormaps\n            hints = colormaphints.checkVolume(\n                ana.cpp.AObjectConverter.aims(obj))\n            obj.attributed()['colormaphints'] = hints\n        bb = obj.boundingbox()\n        # create the 4 windows if they don't exist\n        if len(awindows) == 0:\n            self.createWindow('Coronal')\n            self.createWindow('Axial')\n            self.createWindow('Sagittal')\n            self.createWindow('3D')\n            # set a cool angle of view for 3D\n            a.execute('Camera', windows=[awindows[-1]],\n                      view_quaternion=[0.404603, 0.143829, 0.316813, 0.845718])\n        # view obj in these views\n        self.addObject(obj)\n        # set the cursot at the center of the object (actually, overcome a bug\n        # in anatomist...)\n        position = (aims.Point3df(bb[1][:3]) - bb[0][:3]) / 2.\n        t = a.getTransformation(obj.getReferential(),\n                                awindows[0].getReferential())\n        if t:\n            position = t.transform(position)\n        a.execute('LinkedCursor', window=awindows[0], position=position)\n\n    def addVolume(self, obj, opts={}):\n        '''Display a volume in all windows.\n        If several volumes are displayed, a Fusion2D will be built to wrap all\n        of them.\n        '''\n        global fusion2d\n        if obj in fusion2d:\n            return\n        if len(fusion2d) == 0:\n            # only one object\n            fusion2d = [None, obj]\n        else:\n            # several objects: fusion them\n            fusobjs = fusion2d[1:] + [obj]\n            f2d = a.fusionObjects(fusobjs, method='Fusion2DMethod')\n            if fusion2d[0] is not None:\n                # destroy the previous fusion\n                a.deleteObjects(fusion2d[0])\n            else:\n                a.removeObjects(fusion2d[1], awindows)\n            fusion2d = [f2d] + fusobjs\n            # repalette( fusobjs )\n            obj = f2d\n        if obj.objectType == 'VOLUME':\n            # choose a good colormap for a single volume\n            if 'volume_contents_likelihoods' in \\\n                    obj.attributed()['colormaphints']:\n                cmap = colormaphints.chooseColormaps(\n                    (obj.attributed()['colormaphints'], ))\n                obj.setPalette(cmap[0])\n        else:\n            # choose good colormaps for the current set of volumes\n            hints = [x.attributed()['colormaphints'] for x in obj.children]\n            children = [x for x, y in zip(obj.children, hints)\n                        if 'volume_contents_likelihoods' in y]\n            hints = [x for x in hints\n                     if 'volume_contents_likelihoods' in x]\n            cmaps = colormaphints.chooseColormaps(hints)\n            for x, y in zip(children, cmaps):\n                x.setPalette(y)\n        a.addObjects(obj, awindows, **opts)\n\n    def removeVolume(self, obj, opts={}):\n        '''Hides a volume from views (low-level function: use removeObject)\n        '''\n        global fusion2d\n        if obj in fusion2d:\n            fusobjs = [o for o in fusion2d[1:] if o != obj]\n            if len(fusobjs) >= 2:\n                f2d = a.fusionObjects(fusobjs, method='Fusion2DMethod')\n            else:\n                f2d = None\n            if fusion2d[0] is not None:\n                a.deleteObjects(fusion2d[0])\n            else:\n                a.removeObjects(fusion2d[1], awindows)\n            if len(fusobjs) == 0:\n                fusion2d = []\n            else:\n                fusion2d = [f2d] + fusobjs\n            # repalette( fusobjs )\n            if f2d:\n                obj = f2d\n            elif len(fusobjs) == 1:\n                obj = fusobjs[0]\n            else:\n                return\n            a.addObjects(obj, awindows, **opts)\n\n    def addObject(self, obj):\n        '''Display an object in all windows\n        '''\n        opts = {}\n        if obj.objectType == 'VOLUME':\n            # volumes have a specific function since several volumes have to be\n            # fusionned, and a volume rendering may occur\n            self.addVolume(obj, opts)\n            return\n        elif obj.objectType == 'GRAPH':\n            opts['add_graph_nodes'] = 1\n        a.addObjects(obj, awindows, **opts)\n\n    def removeObject(self, obj):\n        '''Hides an object from views\n        '''\n        if obj.objectType == 'VOLUME':\n            self.removeVolume(obj)\n        else:\n            a.removeObjects(obj, awindows, remove_children=True)\n\n    def fileOpen(self):\n        '''File browser + load object(s)\n        '''\n        fdialog = Qt.QFileDialog()\n        fdialog.setDirectory(self.filedialogdir)\n        fdialog.setFileMode(fdialog.ExistingFiles)\n        res = fdialog.exec_()\n        if res:\n            fnames = fdialog.selectedFiles()\n            self.filedialogdir = fdialog.directory()\n            for fname in fnames:\n                self.loadObject(six.text_type(fname))\n\n    def selectedObjects(self):\n        '''list of objects selected in the list box on the upper left panel\n        '''\n        olist = findChild(awin, 'objectslist')\n        sobjs = []\n        for o in olist.selectedItems():\n            sobjs.append(six.text_type(o.text()).strip('\\0'))\n        return [o for o in aobjects if o.name in sobjs]\n\n    def editAdd(self):\n        '''Display selected objects'''\n        objs = self.selectedObjects()\n        for o in objs:\n            self.addObject(o)\n\n    def editRemove(self):\n        '''Hide selected objects'''\n        objs = self.selectedObjects()\n        for o in objs:\n            self.removeObject(o)\n\n    def editDelete(self):\n        '''Delete selected objects'''\n        objs = self.selectedObjects()\n        for o in objs:\n            self.removeObject(o)\n        olist = findChild(awin, 'objectslist')\n        for o in objs:\n            olist.takeItem(olist.row(olist.findItems(o.name,\n                                                     Qt.Qt.MatchExactly)[0]))\n        global aobjects\n        aobjects = [o for o in aobjects if o not in objs]\n        a.deleteObjects(objs)\n\n    def closeAll(self):\n        '''Exit'''\n        print(\"Exiting\")\n        global vieww, awindows, fusion2d, aobjects, anasimple\n        global awin, viewgridlay\n        # remove windows from their parent to prevent them to be brutally\n        # deleted by Qt.\n        w = None  # ensure there is a w variable before the \"del\" later\n        for w in awindows:\n            w.hide()\n            viewgridlay.removeWidget(w.internalRep._get())\n            w.setParent(None)\n        del w\n        del viewgridlay, vieww\n        del anasimple\n        del awindows, fusion2d, aobjects\n        awin.close()\n        del awin\n        a = ana.Anatomist()\n        a.close()\n\n\n# instantiate the machine\nanasimple = AnaSimpleViewer()\n# connect GUI actions callbacks\nfindChild(awin, 'fileOpenAction').triggered.connect(anasimple.fileOpen)\nfindChild(awin, 'fileExitAction').triggered.connect(anasimple.closeAll)\nfindChild(awin, 'editAddAction').triggered.connect(anasimple.editAdd)\nfindChild(awin, 'editRemoveAction').triggered.connect(anasimple.editRemove)\nfindChild(awin, 'editDeleteAction').triggered.connect(anasimple.editDelete)\n\n# display on the whole screen\nawin.showMaximized()\n\n# tweak: override some user config options\na.config()['setAutomaticReferential'] = 1\na.config()['windowSizeFactor'] = 1.\n\n# run Qt\nif __name__ == '__main__':\n    if run_qt:\n        Qt.QApplication.instance().exec_()\n    elif 'sphinx_gallery' in sys.modules:\n        # load a data\n        awin.showNormal()\n        awin.resize(1000, 700)  # control the size of the snapshot\n        anasimple.loadObject('irm.ima')\n        # these 2 events ensure things are actually drawn\n        Qt.QApplication.instance().processEvents()\n        Qt.QApplication.instance().processEvents()\n        # snapshot the whole widget\n        if Qt.QT_VERSION >= 0x050000:\n            ws = awin.grab()  # Qt5 only\n        else:\n            ws = Qt.QPixmap.grabWidget(awin)  # Qt4 only\n        # openGl areas are not rendered in the snapshot, we have to make them\n        # by hand\n        p = Qt.QPainter(ws)\n        for w in awindows:\n            s = w.snapshotImage()\n            # draw the GL rendering into the image (pixmap)\n            p.drawImage(w.mapTo(awin, Qt.QPoint(0, 0)), s)\n        del w, s, p\n        # snapshot to matplotlib\n        import matplotlib\n        # backend = matplotlib.get_backend()\n        matplotlib.use('agg', force=True)  # force agg\n        from matplotlib import pyplot\n        im = qt_backend.qimage_to_np(ws)\n        plot = pyplot.imshow(im)\n        axes = pyplot.gcf().axes\n        axes[0].get_xaxis().set_visible(False)\n        axes[0].get_yaxis().set_visible(False)\n        pyplot.show(block=False)\n        # matplotlib.use(backend, force=True)  # restore backend\n\n    if close_qt:\n        anasimple.closeAll()"
      ]
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3.11.0"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}