The details of how to achieve this are too big for a quick response here, but I’ll try to get you pointed in some helpful directions and you can always come back and ask for help when you are getting stuck.
At a high level, the idea behind dividing the widget and logic into separate classes is that the widget manages all interaction with GUI elements, while the logic carries out all the functionality (the working-with and modifying data stuff) you want your module to have. The major benefit of this division is that outside code (i.e. modules that other people write) can use the functionality you provide without having to instantiate your widget GUI. Maybe this will be an easy to understand concept for you since it sounds like you have a solid programming background, but this was something I struggled with at first (deciding how functions should be structured). The good news is that, as you are learning, nothing bad happens if you put functionality in the widget class that properly belongs in the logic class. And, as you get used to it, it will steadily become clearer what belongs where.
Some recommendations, steps to take, and resources:
- Enable developer mode in your Slicer settings (Edit menu → Application Settings → Developer → Enable developer mode checkbox). This will show some helpful extra buttons on all python scripted modules which will be very helpful while developing your module. The “Reload” button reloads the module from the source code, which is very helpful when you are actively making and testing changes (usually there’s no need to restart Slicer to test changes). The “Edit” button opens the module source code in your default editor. This can be very useful for looking at other modules’ code as well as your own. The “Edit UI” button launches Qt Designer and allows you to edit the GUI elements of your module in a reasonably intuitive way.
- I highly recommend using Extension Wizard module and starting from there with your first module. The template code there is a simple working module which allows thresholding an input volume. As a minimal first example, I would recommend trying to add a new button to this module and make it do something like add one to all voxel values of the input. In order to do this, you’ll need to look closely at the template code and try to mimic what is done for the existing Apply button. You’ll need to create the button in Qt Designer, connect the button click signal to a callback in your widget’s setup function, write the callback function to gather any needed inputs, pass those inputs to a logic function which does the actual work of taking the input volume node, getting the voxel values, adding one, and either updating the original input volume or creating a new output volume with the new values. This should be a very educational exercise, and by the time you’ve accomplished it, you’ll have a better sense of how Slicer modules typically work and pass around data.
- Next, start thinking about what inputs your functions will need. You will need a selector to choose the segmentation node which contains all your vessel segments, and you will also need the ability to place markups points. For the first, in Qt Designer, you want to add a qMRMLNodeComboBox and set the nodeTypes it allows you to select to be “vtkMRMLSegmentationNode”. For the second, a handy widget is qSlicerMarkupsPlaceWidget. Lastly, you probably want a button which you click once you’ve identified the two segments you want to link and which triggers carrying out the actual linkage.
- That’s probably all you really need on the GUI side. When you click the button, the selected segmentation node and markups node should be passed to a logic function which is supposed to carry out all of the work. You can start off with a stub function there which just lets you know it has been called by printing something out (print statements or logging statements show up in the python interactor in Slicer as well as in the Error Log window (Ctrl-0 or the X in the lower right of the status bar)).
- The rest of the work is actually implementing what you want to happen, given the inputs. The good news is that this is just pure python coding Here are the pieces I see that you will need:
- A function finding the closest segment centerline endpoint to a markups point. I’m not sure what format your centerline data is in after your initial processing, but at minimum, you will need some way of keeping track of which segment goes with which centerline. To find the closest centerline endpoint to your first clicked point, I would just make a list of all the centerline endpoints and calculate the distance to the clicked markups point, and find the one with the minimum distance. For any markups node, you can get control point positions like this.
- A function to take two centerlines and link them at the ends you specify. You can access the coordinates of each centerline point as in the previous section; so the only real complication is getting them in the right order. All of the points from one of the centerlines come first, and then all of the points from the other centerline, either in the original order or reversed, depending on which end is supposed to be connected.
- A function to take two endpoints which are to be newly connected and which creates a new segment connecting them. If you want to base the radius of this segment on reported radii from VMTK, you also need a way of extracting that data (maybe look here, that isn’t something I have done myself before). With a radius and endpoints, you can draw a cylindrical tube using the “Draw tube” segment editor effect. The new segment can then be merged with the two existing segments which are to be joined using logical operators “Add” segmentation effect. Some helpful code examples for logical operations can be found in this forum thread.
- Then you would need to do whatever bookkeeping is needed (deleting the old versions of the merged segments and centerlines and updating with the new merged version)
- Very helpful code snippets for all kinds of things in Slicer can be found in the script repository, this is one of the first places you should look when trying to figure out how to do something in Slicer using python.
- There is also a LOT of information available in the Slicer Developer Guide documentation. It may be helpful to peruse areas of that site, though there’s far too much there to read and digest all in one go.