From c40d492023ffe5d9b611c457ccb1c46b01814af3 Mon Sep 17 00:00:00 2001 From: Kyle Sunderland Date: Fri, 23 Jul 2021 14:46:27 -0400 Subject: [PATCH] ENH: Add base class for scripted ultrasound parameter widgets --- .../UltrasoundRemoteControl.py | 191 ++++++++++++++++++ 1 file changed, 191 insertions(+) diff --git a/UltrasoundRemoteControl/UltrasoundRemoteControl.py b/UltrasoundRemoteControl/UltrasoundRemoteControl.py index d3d8324..d49aa39 100644 --- a/UltrasoundRemoteControl/UltrasoundRemoteControl.py +++ b/UltrasoundRemoteControl/UltrasoundRemoteControl.py @@ -1,6 +1,7 @@ import os.path, datetime from __main__ import vtk, qt, ctk, slicer from slicer.ScriptedLoadableModule import * +from slicer.util import VTKObservationMixin import logging # @@ -189,3 +190,193 @@ def __init__(self, parent = None): def __del__(self): # Clean up commands pass + +# +# Abstract widget that can be used as a base class to create custom scripted ultrasound widgets. +# TODO: In the future it would be better to make it so that we can override the virtual methods of qSlicerAbstractUltrasoundParameterWidget +# in Python and use that as a base class instead. +class AbstractScriptedUltrasoundWidget(qt.QWidget, VTKObservationMixin): + def __init__(self): + qt.QWidget.__init__(self) + VTKObservationMixin.__init__(self) + self._cmdSetParameter = None + self._cmdGetParameter = None + self._interactionInProgress = False + self._deviceID = "" + self._parameterName = "" + self._connectorNode = None + self._periodicParameterTimer = qt.QTimer() + self._periodicParameterTimer.connect("timeout()", self.sendGetUltrasoundParameterCommand) + + def setInteractionInProgress(self, interactionStatus): + """ + Returns True if the user is currently interacting with the widget. + Ex. Dragging a slider + """ + self._interactionInProgress = interactionStatus + + def interactionInProgress(self): + """ + Returns True if the user is currently interacting with the widget. + Ex. Dragging a slider + """ + return self._interactionInProgress + + def connectorNode(self): + """ + Get the connector node that will be connected to the ultrasound device. + """ + return self.connectorNode + + def setConnectorNode(self, node): + """ + Set the connector node that will be connected to the ultrasound device. + """ + self.removeObservers(self.onConnectionChanged) + if node: + self.addObserver(node, slicer.vtkMRMLIGTLConnectorNode.ConnectedEvent, self.onConnectionChanged) + self.addObserver(node, slicer.vtkMRMLIGTLConnectorNode.DisconnectedEvent, self.onConnectionChanged) + self._connectorNode = node + self.onConnectionChanged() + + def onConnectionChanged(self, caller=None, event=None, callData=None): + """ + Called when the connector node is changed, or if the device is (dis)connected. + """ + if self._connectorNode and self._connectorNode.GetState() == slicer.vtkMRMLIGTLConnectorNode.StateConnected: + self.onConnected() + self.sendGetUltrasoundParameterCommand() + self._periodicParameterTimer.start(2000) + else: + self._periodicParameterTimer.stop() + self.onDisconnected() + + def setDeviceID(self, deviceID): + """ + Set the device id from the Plus config file that will be used to set/get the ultrasound parameters. + Ex. "VideoDevice" + """ + self._deviceID = deviceID + + def deviceID(self): + """ + Get the device id from the Plus config file that will be used to set/get the ultrasound parameters. + Ex. "VideoDevice" + """ + return self._deviceID + + def sendSetUltrasoundParameterCommand(self): + """ + Send a Set command to the ultrasound to request a change in parameter. + Calls onSetUltrasoundParameterCompleted when the command has completed. + """ + if self._connectorNode is None or self._connectorNode.GetState() != slicer.vtkMRMLIGTLConnectorNode.StateConnected: + return + setUSParameterXML = """ + + + + """.format(deviceID=self._deviceID, parameterName=self._parameterName, parameterValue=self.parameterValue()) + self._cmdSetParameter = slicer.vtkSlicerOpenIGTLinkCommand() + self._cmdSetParameter.SetName("SetUsParameter") + self._cmdSetParameter.SetTimeoutSec(5.0) + self._cmdSetParameter.SetBlocking(False) + self._cmdSetParameter.SetCommandContent(setUSParameterXML) + self._cmdSetParameter.ClearResponseMetaData() + self.setInteractionInProgress(True) + self.removeObservers(self.onSetUltrasoundParameterCompleted) + self.addObserver(self._cmdSetParameter, slicer.vtkSlicerOpenIGTLinkCommand.CommandCompletedEvent, self.onSetUltrasoundParameterCompleted) + self._connectorNode.SendCommand(self._cmdSetParameter) + + def onSetUltrasoundParameterCompleted(self, caller=None, event=None, callData=None): + """ + Called when SetUsParameter has completed. + """ + self._interactionInProgress = False + self.sendGetUltrasoundParameterCommand() # Confirm new value + + def sendGetUltrasoundParameterCommand(self): + """ + Send a Get command to the ultrasound requesting a parameter value. + Calls onGetUltrasoundParameterCompleted when the command has completed. + """ + if self._connectorNode is None or self._connectorNode.GetState() != slicer.vtkMRMLIGTLConnectorNode.StateConnected: + return + if self._cmdGetParameter and self._cmdGetParameter.IsInProgress(): + # Get command is already in progress + return + getUSParameterXML = """ + + + + """.format(deviceID=self._deviceID, parameterName=self._parameterName) + self._cmdGetParameter = slicer.vtkSlicerOpenIGTLinkCommand() + self._cmdGetParameter.SetName("GetUsParameter") + self._cmdGetParameter.SetTimeoutSec(5.0) + self._cmdGetParameter.SetBlocking(False) + self._cmdGetParameter.SetCommandContent(getUSParameterXML) + self._cmdGetParameter.ClearResponseMetaData() + self.removeObservers(self.onGetUltrasoundParameterCompleted) + self.addObserver(self._cmdGetParameter, slicer.vtkSlicerOpenIGTLinkCommand.CommandCompletedEvent, self.onGetUltrasoundParameterCompleted) + self._connectorNode.SendCommand(self._cmdGetParameter) + + def onGetUltrasoundParameterCompleted(self, caller=None, event=None, callData=None): + """ + Called when GetUsParameter has completed. + """ + if not self._cmdGetParameter.GetSuccessful(): + return + if self._interactionInProgress or self._cmdGetParameter.IsInProgress(): + # User is currently interacting with the widget or + # SetUSParameterCommand is currently in progress + # Don't update the current value + return + value = self._cmdGetParameter.GetResponseMetaDataElement(self._parameterName) + self.setParameterValue(value) # Set the value in the widget + + def parameterName(self): + """ + Get name of the ultrasound parameter being controlled. + Ex. "DepthMm" + """ + return self.parameterName + + def setParameterName(self, parameterName): + """ + Set name of the ultrasound parameter being controlled. + Ex. "DepthMm" + """ + self._parameterName = parameterName + + def onConnected(self): + """ + Called when a connection has been established. + Not required to override for widget to function. + """ + pass + + def onDisconnected(self): + """ + Called when a device has been disconnected, or when the connection has not been set. + Not required to override for widget to function. + """ + pass + + ################################################### + # These methods should be overridden by subclasses. + def parameterValue(self): + """ + Return the parameter value from the widget. + Ex. Slider/spinbox value, or any value set by widget. + Required to override for widget to function. + """ + return "" + + def setParameterValue(self, value): + """ + Update the state of the widget to reflect the specified parameter value. + Ex. Slider/spinbox value, or any value set by widget. + Required to override for widget to function. + """ + pass + ###################################################