I would like to develop a Python script that allows the deletion of multiple control points within a ROI. Specifically, it would be great to mimic the behavior of the Scissors tool from the Segment Editor module.
Hi, thank you very much! I’m running some tests with this tool. However, the selection of points isn’t accurate when there’s overlap between points or with a curve. Additionally, sometimes points outside the ROI get selected.
The steps I’m following are:
Pick points with curve → Set selection → draw ROI → Edit selected points → Delete points
I also noticed that it only takes into account the points that are visible in the 3D view, which becomes a problem when the view is zoomed in on a specific region.
Yes, I observed similar issues occasionally as well. But often drawing another curve and choosing “Add to Selection” fixes it, so we didn’t spend too much debugging it.
You are welcome to take look and improve if you have the bandwidth.
Yes, that’s a requirement for our use case, in which we often have points on a model. If you don’t restrict it to visible points only, it will select points on the back side of the model too.
I’ve reviewed the code, and for our specific purpose, we need to ensure that points outside the view are not selected, and that the ROI restricts the selection similarly to how the scissors tool works in Segment Editor. To achieve this, I modified the logic class in the MarkupEditor.py file (look for comments marked “CHANGED”):
Removed if pointVisible: in the selection option.
Added a check to determine if a point is outside the 3D view; if so, it is marked as unselected.
class MarkupEditorLogic(ScriptedLoadableModuleLogic):
def __init__(self):
ScriptedLoadableModuleLogic.__init__(self)
self.observerTags = {}
def editMarkups(self, selectOption, fiducialsNode, curveNode, viewNode):
layoutManager = slicer.app.layoutManager()
threeDWidget = layoutManager.viewWidget(viewNode)
threeDView = threeDWidget.threeDView()
aspectRatio = threeDWidget.width / threeDWidget.height
className = "vtkMRMLMarkupsDisplayableManager"
markupsDisplayableManager = threeDView.displayableManagerByClassName(className)
fiducialsWidget = markupsDisplayableManager.GetWidget(fiducialsNode.GetDisplayNode())
fiducialsRepresentation = fiducialsWidget.GetRepresentation()
cameraNode = slicer.modules.cameras.logic().GetViewActiveCameraNode(viewNode)
camera = cameraNode.GetCamera()
raswToXYZW = vtk.vtkMatrix4x4()
modelView = camera.GetModelViewTransformMatrix()
projection = camera.GetProjectionTransformMatrix(aspectRatio, 1, 100) # near/far are arbitrary
raswToXYZW.Multiply4x4(projection, modelView, raswToXYZW)
def rasToColumnRow(ras):
rasw = *ras, 1
xyzw = raswToXYZW.MultiplyPoint(rasw)
x, y = [xyzw[0], xyzw[1]]
if viewNode.GetRenderMode() == viewNode.Perspective:
x, y = [x / xyzw[3], y / xyzw[3]]
column = (x + 1) / 2 * threeDWidget.width
row = (1 - (y + 1) / 2) * threeDWidget.height
return column, row
selectionPolygon = qt.QPolygonF()
for index in range(curveNode.GetNumberOfControlPoints()):
ras = [0] * 3
curveNode.GetNthControlPointPositionWorld(index, ras)
column, row = rasToColumnRow(ras)
point = qt.QPointF(column, row)
selectionPolygon.push_back(point)
pickImage = qt.QImage(threeDWidget.width, threeDWidget.height, qt.QImage().Format_ARGB32)
backgroundColor = qt.QColor("#ffffffff")
pickImage.fill(backgroundColor)
painter = qt.QPainter()
painter.begin(pickImage)
painterPath = qt.QPainterPath()
painterPath.addPolygon(selectionPolygon)
brush = qt.QBrush(qt.Qt.SolidPattern)
painter.setBrush(brush)
painter.drawPath(painterPath)
painter.end()
debugging = False
if debugging:
pixmap = qt.QPixmap().fromImage(pickImage)
slicer.modules.label = qt.QLabel()
slicer.modules.label.setPixmap(pixmap)
slicer.modules.label.show()
visibleCount = 0
pickedCount = 0
for index in range(fiducialsNode.GetNumberOfControlPoints()):
pointVisible = fiducialsRepresentation.GetNthControlPointViewVisibility(index)
if pointVisible:
visibleCount += 1
ras = [0] * 3
fiducialsNode.GetNthControlPointPositionWorld(index, ras)
column, row = rasToColumnRow(ras)
# CHANGED: check if the point is inside the 3D view
if column < 0 or column >= pickImage.width() or row < 0 or row >= pickImage.height():
fiducialsNode.SetNthControlPointSelected(index, False)
continue # we do not process this point
# If the point is inside the 3D view, we check if it is inside
# the polygon
pickColor = pickImage.pixelColor(column, row)
picked = (pickColor != backgroundColor)
if picked:
pickedCount += 1
# CHANGED: we do not use pointVisible here, so overlapping points are selected
if selectOption == "set":
fiducialsNode.SetNthControlPointSelected(index, picked)
elif selectOption == "add":
if picked:
fiducialsNode.SetNthControlPointSelected(index, True)
elif selectOption == "unset":
if picked:
fiducialsNode.SetNthControlPointSelected(index, False)
else:
logging.error(f"Unknown selectOption {selectOption}")