knossos
knossos copied to clipboard
Custom operation modes
Currently there are two operation modes - segmentation and skeletonization. In both modes all overlay and data objects are in place, the only difference is the behavior of the GUI: What the mouse cursor looks like, which event handlers are dispatched upon keyboard/mouse clicks. This is implemented in a hard-coded way.
I suggest generalizing the concept of an operation mode, to accustom different use cases. Each mode would be registered by whoever implements it (the skel/seg current handlers, as well as other C++ code, and most prominently different plugins), which the complete set of keyboard and mouse bindings. Current implementation of keyboard and mouse bindings should be uncoupled from their respective hard-coded modes, to allow usage by other custom modes. For example, the user would be able to create a MySkelMode which is copied from skeletonization mode but adds or modifies some key bindings. Alternatively, MyBucketFillMode would be a completely new mode, which implements mostly its own bindings, but still allows normal data browsing using the Left/Right/F/D keys etc.
All operation modes would be dynamically (un)registered and (de)activated as needed, and possibly saved to settings. The only major difficulty is to ensure that upon loading a stored registered mode, the implementation (e.g. a certain plugin) is also available. To circumvent this, we may only allow loading an operation mode from the code which actually provides the bound implementation. For example, if a Bucket Filler plugin registers a Bucket Fill mode, this mode could only be loaded by the plugin, and not arbitrarily by the user. Therefore, the user would only be able to load an operation mode that "rewires" elements of internal modes (i.e. Skel+Seg), e.g. replacing "F" with "D" or triggering different triggers for existing implementations, e.g.
The above is not anarchism gone wild, it's just a normal feature of any common work environment.
Implementation:
Begin by reworking the MainWindow+EventModel to ensure a proper tree of event handling
- Mouse 1.1 Release/Press 1.1.1 Left Button 1.1.2 Middle Button 1.1.3 Right Button 1.2 Roll 1.2.1 Up 1.2.2 Down 1.3 Move
- Keyboard 2.1 Press 2.1.1 Key_A 2.1.2 Key_B 2.1.3 Key_F2 2.1.4 etc. 2.2 Release 2.2.1 Key_A 2.2.2 etc. The leaf of each branch would be the modifier check - shift/ctrl/alt/etc. Once this rework has been done, each leaf would be relocated to two different functions - LEAF_NAME_Seg and LEAF_NAME_Skel - e.g. MOUSE_RELEASE_LEFT_SHIFT_ALT_Seg - containing respective implementation for each mode. "SegMode and "SkelMode" classes would instantiate from MainWindow upon initialization, and register each of their handler functions with the MainWindow, for example in SegMode ctor: mainwindow->RegisterHandler("Mouse", "Release", "Left", Qt::Shift | Qt::Alt, "Seg", MOUSE_RELEASE_LEFT_SHIFT_ALT_Seg); Internally, the RegisterHandler function would hash "Mouse","Release","Left" as first key, Qt::Shift | Qt::Alt as second key. and "Seg" as third key. The combined triple key-tuple would serve as key into a named hash, where the value would be set to MOUSE_RELEASE_LEFT_SHIFT_ALT_Seg. When the mouse left-release event happens, MainWindow would hash the event name, together with the current modifier state, e.g. Qt::Shift | Qt::Alt, and the current operation mode, e.g. Seg, retrieve the corresponding function from the hash, and call it. UnregisterHandler would remove the function from the hash. In addition, a common basic mode (e.g. data browsing) can be implemented by defining handler inheritance, i.e. both SegMode and SkelMode classes would subclass a CommonMode whose ctor registers its handler using the subclass' name. Alternatively, a plugin would subclass SegMode to implement handling which is similar to SegMode, yet overriding some events with a custom behavior by called UnregisterHandler. For both the above to work, a virtual getHandlerName function would be used by the event handler class, and overridden by subclassees. We have to carefully see how to exactly allow this from PythonQt, given that Python subclassing of C++ classes would require proper decoration. The alternative for python would be allowing direct access into the registered handler functions of Seg/SkelModes, either by directly returning their hash values, or using DuplicateHanlder functions that would place a relay from one mode hash value to another's. It's better not to actually copy the function pointers, because if the FooPluginMode left-mouse-click handler actually contained reference to BarPluginMode and then BarPlugin goes down, we wouldn't want FooPlugin to break.
While this feature should foremost handle keyboard and mouse, the named hash approach should also allow responding to knossos events (that are currently hard-coded, but custom events may be added in the future by plugins), i.e. placing nodes, erasing comments, merging segmentation objects, etc. This is currently done implicitly, as nodes are only placed in skeletonization mode, therefore the handler for placing a node is hard-wired from the node-placing function. But such limitation should not apply in the future, most notably since the clear-cut separation between "skeleonization" and "segmentation" would be lost as custom modes are employed by plugins. In fact, to some extent it already is, to some extent, by the recently-added, hard-coded "hybrid mode", and by the ability to change segmentation through the annotation widget even when in skeletonization mode.
The main problem would be defining proper prototypes for handlers, given the arbitrary nature of an event, in a way that could still be referenced from an opaque function pointer. While handler functions written in C++ can cast this pointer in order to pass event-specific arguments, slots registered by python would not be typed in C++ in compile time, therefore another method for passing event-specific arguments would be required, possibly by event-specific data classes and their respective python decorations.