<filename>ImportOsirixROI/ImportOsirixROI.py<gh_stars>10-100
import os
import unittest
import logging
import vtk, qt, ctk, slicer
from slicer.ScriptedLoadableModule import *
from slicer.util import VTKObservationMixin

#
# ImportOsirixROI
#

class ImportOsirixROI(ScriptedLoadableModule):
  """Uses ScriptedLoadableModule base class, available at:
  https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
  """

  def __init__(self, parent):
    ScriptedLoadableModule.__init__(self, parent)
    self.parent.title = "Import OsiriX ROI"
    self.parent.categories = ["Utilities"]
    self.parent.dependencies = []
    self.parent.contributors = ["<NAME> (Queen's, PerkLab)"]
    self.parent.helpText = """
Import Osirix ROI files to Slicer segmentation.
"""
    self.parent.helpText += self.getDefaultModuleDocumentationLink()  # TODO: verify that the default URL is correct or change it to the actual documentation
    self.parent.acknowledgementText = """
This file was originally developed by <NAME>, PerkLab.
"""

#
# ImportOsirixROIWidget
#

class ImportOsirixROIWidget(ScriptedLoadableModuleWidget, VTKObservationMixin):
  """Uses ScriptedLoadableModuleWidget base class, available at:
  https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
  """

  def __init__(self, parent=None):
    """
    Called when the user opens the module the first time and the widget is initialized.
    """
    ScriptedLoadableModuleWidget.__init__(self, parent)
    VTKObservationMixin.__init__(self)  # needed for parameter node observation
    self.logic = None
    self._parameterNode = None

  def setup(self):
    """
    Called when the user opens the module the first time and the widget is initialized.
    """
    ScriptedLoadableModuleWidget.setup(self)

    # Load widget from .ui file (created by Qt Designer)
    uiWidget = slicer.util.loadUI(self.resourcePath('UI/ImportOsirixROI.ui'))
    self.layout.addWidget(uiWidget)
    self.ui = slicer.util.childWidgetVariables(uiWidget)

    # Set scene in MRML widgets. Make sure that in Qt designer
    # "mrmlSceneChanged(vtkMRMLScene*)" signal in is connected to each MRML widget's.
    # "setMRMLScene(vtkMRMLScene*)" slot.
    uiWidget.setMRMLScene(slicer.mrmlScene)

    # Create a new parameterNode
    # This parameterNode stores all user choices in parameter values, node selections, etc.
    # so that when the scene is saved and reloaded, these settings are restored.
    self.logic = ImportOsirixROILogic()
    self.ui.parameterNodeSelector.addAttribute("vtkMRMLScriptedModuleNode", "ModuleName", self.moduleName)
    self.setParameterNode(self.logic.getParameterNode())

    # Connections
    self.ui.parameterNodeSelector.connect('currentNodeChanged(vtkMRMLNode*)', self.setParameterNode)
    self.ui.applyButton.connect('clicked(bool)', self.onApplyButton)

    # These connections ensure that whenever user changes some settings on the GUI, that is saved in the MRML scene
    # (in the selected parameter node).
    
    self.ui.inputRoiPathLineEdit.connect("currentPathChanged(QString)", self.updateParameterNodeFromGUI)
    self.ui.outputSelector.connect("currentNodeChanged(vtkMRMLNode*)", self.updateParameterNodeFromGUI)

    # Initial GUI update
    self.updateGUIFromParameterNode()

  def cleanup(self):
    """
    Called when the application closes and the module widget is destroyed.
    """
    self.removeObservers()

  def setParameterNode(self, inputParameterNode):
    """
    Adds observers to the selected parameter node. Observation is needed because when the
    parameter node is changed then the GUI must be updated immediately.
    """

    if inputParameterNode:
      self.logic.setDefaultParameters(inputParameterNode)

    # Set parameter node in the parameter node selector widget
    wasBlocked = self.ui.parameterNodeSelector.blockSignals(True)
    self.ui.parameterNodeSelector.setCurrentNode(inputParameterNode)
    self.ui.parameterNodeSelector.blockSignals(wasBlocked)

    if inputParameterNode == self._parameterNode:
      # No change
      return

    # Unobserve previusly selected parameter node and add an observer to the newly selected.
    # Changes of parameter node are observed so that whenever parameters are changed by a script or any other module
    # those are reflected immediately in the GUI.
    if self._parameterNode is not None:
      self.removeObserver(self._parameterNode, vtk.vtkCommand.ModifiedEvent, self.updateGUIFromParameterNode)
    if inputParameterNode is not None:
      self.addObserver(inputParameterNode, vtk.vtkCommand.ModifiedEvent, self.updateGUIFromParameterNode)
    self._parameterNode = inputParameterNode

    # Initial GUI update
    self.updateGUIFromParameterNode()

  def updateGUIFromParameterNode(self, caller=None, event=None):
    """
    This method is called whenever parameter node is changed.
    The module GUI is updated to show the current state of the parameter node.
    """

    # Disable all sections if no parameter node is selected
    self.ui.basicCollapsibleButton.enabled = self._parameterNode is not None
    if self._parameterNode is None:
      return

    # Update each widget from parameter node
    # Need to temporarily block signals to prevent infinite recursion (MRML node update triggers
    # GUI update, which triggers MRML node update, which triggers GUI update, ...)

    wasBlocked = self.ui.inputRoiPathLineEdit.blockSignals(True)
    self.ui.inputRoiPathLineEdit.currentPath = self._parameterNode.GetParameter("InputRoiFilePath")
    self.ui.inputRoiPathLineEdit.blockSignals(wasBlocked)

    wasBlocked = self.ui.outputSelector.blockSignals(True)
    self.ui.outputSelector.setCurrentNode(self._parameterNode.GetNodeReference("OutputSegmentation"))
    self.ui.outputSelector.blockSignals(wasBlocked)

    # Update buttons states and tooltips
    if self._parameterNode.GetParameter("InputRoiFilePath") and self._parameterNode.GetNodeReference("OutputSegmentation"):
      self.ui.applyButton.toolTip = "Convert"
      self.ui.applyButton.enabled = True
    else:
      self.ui.applyButton.toolTip = "Select input and output files"
      self.ui.applyButton.enabled = False

  def updateParameterNodeFromGUI(self, caller=None, event=None):
    """
    This method is called when the user makes any change in the GUI.
    The changes are saved into the parameter node (so that they are restored when the scene is saved and loaded).
    """

    if self._parameterNode is None:
      return

    self._parameterNode.SetParameter("InputRoiFilePath", self.ui.inputRoiPathLineEdit.currentPath)
    self._parameterNode.SetNodeReferenceID("OutputSegmentation", self.ui.outputSelector.currentNodeID)

  def onApplyButton(self):
    """
    Run processing when user clicks "Apply" button.
    """

    if not hasattr(slicer.modules, 'dicomrtimportexport'):
      slicer.util.warningDisplay("SlicerRT extension is not installed. Segmentation will be imported as a set of parallel contours. Install SlicerRT extension to create segmentation as a solid object.")

    self.ui.inputRoiPathLineEdit.addCurrentPathToHistory()
    try:
      self.logic.importOsirixRoiFileToSegmentation(self.ui.inputRoiPathLineEdit.currentPath, self._parameterNode.GetNodeReference("OutputSegmentation"))
    except Exception as e:
      slicer.util.errorDisplay("Import failed: "+str(e))
      import traceback
      traceback.print_exc()


#
# ImportOsirixROILogic
#

class ImportOsirixROILogic(ScriptedLoadableModuleLogic):
  """This class should implement all the actual
  computation done by your module.  The interface
  should be such that other python code can import
  this class and make use of the functionality without
  requiring an instance of the Widget.
  Uses ScriptedLoadableModuleLogic base class, available at:
  https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
  """

  def setDefaultParameters(self, parameterNode):
    """
    Initialize parameter node with default settings.
    """
    pass

  def importOsirixRoiFileToSegmentation(self, inputRoi, outputSegmentationNode):
    """
    Run the processing algorithm.
    Can be used without GUI widget.
    :param inputRoi: input OsiriX ROI file path or text node
    :param outputSegmentation: output segmentation node
    """

    if not inputRoi or not outputSegmentationNode:
      raise ValueError("Input file or output segmentation node is invalid")

    logging.info('Processing started')

    inputRoiData = None
    import json
    import plistlib
    if isinstance(inputRoi,str):
      filename, ext = os.path.splitext(inputRoi)
      if ext.lower() == '.json':
        with open(inputRoi) as f:
          inputRoiData = json.load(f)
      else:
        inputRoiData = plistlib.readPlist(inputRoi)
    elif isinstance(inputRoi, slicer.vtkMRMLTextNode):
      try:
        inputRoiData = json.loads(inputRoi.GetText())
      except:
        inputRoiData = plistlib.readPlistFromBytes(inputRoi.GetText())
    else:
      raise TypeError("inputRoi is expected to be a string or vtkMRMLTextNode")

    outputSegmentationNode.CreateDefaultDisplayNodes()
    segmentation = outputSegmentationNode.GetSegmentation()
    segmentation.SetMasterRepresentationName(slicer.vtkSegmentationConverter.GetPlanarContourRepresentationName())

    roiContourPoints = vtk.vtkPoints()
    roiContourCells = vtk.vtkCellArray()

    name = None
    color = [ 1.0, 0.0, 0.0 ]

    if "Images" not in inputRoiData:
      for contour in inputRoiData:
        roiPoints = contour["ROI3DPoints"]
        cellPointIds = []
        for roiPoint in roiPoints:
          roiPointStrList = roiPoint.strip('[]').split(',')
          cellPointIds.append(roiContourPoints.InsertNextPoint(-float(roiPointStrList[0]), -float(roiPointStrList[1]), float(roiPointStrList[2])))
        contourIndex = roiContourCells.InsertNextCell(len(cellPointIds)+1)
        for cellPointId in cellPointIds:
          roiContourCells.InsertCellPoint(cellPointId)
        roiContourCells.InsertCellPoint(cellPointIds[0])  # close the contour
      name = inputRoiJson[0]["Name"]
    else:
      # Output of Export ROIs plugin
      for image in inputRoiData['Images']:
        rois = image['ROIs']
        for roi in rois:
          if not name:
            if roi['Name']:
              name = roi['Name']
          roiPoints = roi['Point_mm']
          cellPointIds = []
          for roiPoint in roiPoints:
            roiPointStrList = roiPoint.strip('()').split(',')
            cellPointIds.append(roiContourPoints.InsertNextPoint(-float(roiPointStrList[0]), -float(roiPointStrList[1]), float(roiPointStrList[2])))
          contourIndex = roiContourCells.InsertNextCell(len(cellPointIds)+1)
          for cellPointId in cellPointIds:
            roiContourCells.InsertCellPoint(cellPointId)
          roiContourCells.InsertCellPoint(cellPointIds[0])  # close the contour

    roiPolyData = vtk.vtkPolyData()
    roiPolyData.SetPoints(roiContourPoints)
    roiPolyData.SetLines(roiContourCells)

    segment = slicer.vtkSegment()
    segment.SetName(name)
    segment.SetColor(color)
    segment.AddRepresentation(slicer.vtkSegmentationConverter.GetPlanarContourRepresentationName(), roiPolyData)

    segmentation.AddSegment(segment)

    outputSegmentationNode.CreateBinaryLabelmapRepresentation()
    outputSegmentationNode.CreateClosedSurfaceRepresentation()
    outputSegmentationNode.GetDisplayNode().SetPreferredDisplayRepresentationName2D(slicer.vtkSegmentationConverter.GetBinaryLabelmapRepresentationName())
    outputSegmentationNode.GetDisplayNode().SetPreferredDisplayRepresentationName3D(slicer.vtkSegmentationConverter.GetClosedSurfaceRepresentationName())

    logging.info('Processing completed')

#
# ImportOsirixROITest
#

class ImportOsirixROITest(ScriptedLoadableModuleTest):
  """
  This is the test case for your scripted module.
  Uses ScriptedLoadableModuleTest base class, available at:
  https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
  """

  def setUp(self):
    """ Do whatever is needed to reset the state - typically a scene clear will be enough.
    """
    slicer.mrmlScene.Clear(0)

  def runTest(self):
    """Run as few or as many tests as needed here.
    """
    self.setUp()
    self.test_ImportOsirixROI1()

  def test_ImportOsirixROI1(self):
    """ Ideally you should have several levels of tests.  At the lowest level
    tests should exercise the functionality of the logic with different inputs
    (both valid and invalid).  At higher levels your tests should emulate the
    way the user would interact with your code and confirm that it still works
    the way you intended.
    One of the most important features of the tests is that it should alert other
    developers when their changes will have an impact on the behavior of your
    module.  For example, if a developer removes a feature that you depend on,
    your test should break so they know that the feature is needed.
    """

    self.delayDisplay("Starting the test")

    # Get/create input data

    import SampleData
    jsonTextNode = SampleData.downloadFromURL(
      nodeNames='OsirixTestROI_1',
      fileNames='OsirixTestROI_1.json',
      loadFileTypes='TextFile',
      uris='https://raw.githubusercontent.com/PerkLab/SlicerSandbox/master/ImportOsirixROI/Testing/Data/OsirixTestROI_1.json',
      checksums='MD5:0ffb20a8802b08884753b5f4c0cb18c9')[0]

    self.delayDisplay('Finished with download and loading')

    outputSegmentation = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLSegmentationNode")

    # Test the module logic
    logic = ImportOsirixROILogic()
    logic.importOsirixRoiFileToSegmentation(jsonTextNode, outputSegmentation)
    self.assertEqual(outputSegmentation.GetSegmentation().GetNumberOfSegments(), 1)
    self.assertEqual(outputSegmentation.GetClosedSurfaceInternalRepresentation("Test").GetNumberOfPoints(), 2935)

    self.delayDisplay('Test passed')

<filename>ImportOsirixROI/ImportOsirixROI.py<gh_stars>10-100
import os
import unittest
import logging
import vtk, qt, ctk, slicer
from slicer.ScriptedLoadableModule import *
from slicer.util import VTKObservationMixin

#
# ImportOsirixROI
#

class ImportOsirixROI(ScriptedLoadableModule):
  """Uses ScriptedLoadableModule base class, available at:
  https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
  """

  def __init__(self, parent):
    ScriptedLoadableModule.__init__(self, parent)
    self.parent.title = "Import OsiriX ROI"
    self.parent.categories = ["Utilities"]
    self.parent.dependencies = []
    self.parent.contributors = ["<NAME> (Queen's, PerkLab)"]
    self.parent.helpText = """
Import Osirix ROI files to Slicer segmentation.
"""
    self.parent.helpText += self.getDefaultModuleDocumentationLink()  # TODO: verify that the default URL is correct or change it to the actual documentation
    self.parent.acknowledgementText = """
This file was originally developed by <NAME>, PerkLab.
"""

#
# ImportOsirixROIWidget
#

class ImportOsirixROIWidget(ScriptedLoadableModuleWidget, VTKObservationMixin):
  """Uses ScriptedLoadableModuleWidget base class, available at:
  https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
  """

  def __init__(self, parent=None):
    """
    Called when the user opens the module the first time and the widget is initialized.
    """
    ScriptedLoadableModuleWidget.__init__(self, parent)
    VTKObservationMixin.__init__(self)  # needed for parameter node observation
    self.logic = None
    self._parameterNode = None

  def setup(self):
    """
    Called when the user opens the module the first time and the widget is initialized.
    """
    ScriptedLoadableModuleWidget.setup(self)

    # Load widget from .ui file (created by Qt Designer)
    uiWidget = slicer.util.loadUI(self.resourcePath('UI/ImportOsirixROI.ui'))
    self.layout.addWidget(uiWidget)
    self.ui = slicer.util.childWidgetVariables(uiWidget)

    # Set scene in MRML widgets. Make sure that in Qt designer
    # "mrmlSceneChanged(vtkMRMLScene*)" signal in is connected to each MRML widget's.
    # "setMRMLScene(vtkMRMLScene*)" slot.
    uiWidget.setMRMLScene(slicer.mrmlScene)

    # Create a new parameterNode
    # This parameterNode stores all user choices in parameter values, node selections, etc.
    # so that when the scene is saved and reloaded, these settings are restored.
    self.logic = ImportOsirixROILogic()
    self.ui.parameterNodeSelector.addAttribute("vtkMRMLScriptedModuleNode", "ModuleName", self.moduleName)
    self.setParameterNode(self.logic.getParameterNode())

    # Connections
    self.ui.parameterNodeSelector.connect('currentNodeChanged(vtkMRMLNode*)', self.setParameterNode)
    self.ui.applyButton.connect('clicked(bool)', self.onApplyButton)

    # These connections ensure that whenever user changes some settings on the GUI, that is saved in the MRML scene
    # (in the selected parameter node).
    
    self.ui.inputRoiPathLineEdit.connect("currentPathChanged(QString)", self.updateParameterNodeFromGUI)
    self.ui.outputSelector.connect("currentNodeChanged(vtkMRMLNode*)", self.updateParameterNodeFromGUI)

    # Initial GUI update
    self.updateGUIFromParameterNode()

  def cleanup(self):
    """
    Called when the application closes and the module widget is destroyed.
    """
    self.removeObservers()

  def setParameterNode(self, inputParameterNode):
    """
    Adds observers to the selected parameter node. Observation is needed because when the
    parameter node is changed then the GUI must be updated immediately.
    """

    if inputParameterNode:
      self.logic.setDefaultParameters(inputParameterNode)

    # Set parameter node in the parameter node selector widget
    wasBlocked = self.ui.parameterNodeSelector.blockSignals(True)
    self.ui.parameterNodeSelector.setCurrentNode(inputParameterNode)
    self.ui.parameterNodeSelector.blockSignals(wasBlocked)

    if inputParameterNode == self._parameterNode:
      # No change
      return

    # Unobserve previusly selected parameter node and add an observer to the newly selected.
    # Changes of parameter node are observed so that whenever parameters are changed by a script or any other module
    # those are reflected immediately in the GUI.
    if self._parameterNode is not None:
      self.removeObserver(self._parameterNode, vtk.vtkCommand.ModifiedEvent, self.updateGUIFromParameterNode)
    if inputParameterNode is not None:
      self.addObserver(inputParameterNode, vtk.vtkCommand.ModifiedEvent, self.updateGUIFromParameterNode)
    self._parameterNode = inputParameterNode

    # Initial GUI update
    self.updateGUIFromParameterNode()

  def updateGUIFromParameterNode(self, caller=None, event=None):
    """
    This method is called whenever parameter node is changed.
    The module GUI is updated to show the current state of the parameter node.
    """

    # Disable all sections if no parameter node is selected
    self.ui.basicCollapsibleButton.enabled = self._parameterNode is not None
    if self._parameterNode is None:
      return

    # Update each widget from parameter node
    # Need to temporarily block signals to prevent infinite recursion (MRML node update triggers
    # GUI update, which triggers MRML node update, which triggers GUI update, ...)

    wasBlocked = self.ui.inputRoiPathLineEdit.blockSignals(True)
    self.ui.inputRoiPathLineEdit.currentPath = self._parameterNode.GetParameter("InputRoiFilePath")
    self.ui.inputRoiPathLineEdit.blockSignals(wasBlocked)

    wasBlocked = self.ui.outputSelector.blockSignals(True)
    self.ui.outputSelector.setCurrentNode(self._parameterNode.GetNodeReference("OutputSegmentation"))
    self.ui.outputSelector.blockSignals(wasBlocked)

    # Update buttons states and tooltips
    if self._parameterNode.GetParameter("InputRoiFilePath") and self._parameterNode.GetNodeReference("OutputSegmentation"):
      self.ui.applyButton.toolTip = "Convert"
      self.ui.applyButton.enabled = True
    else:
      self.ui.applyButton.toolTip = "Select input and output files"
      self.ui.applyButton.enabled = False

  def updateParameterNodeFromGUI(self, caller=None, event=None):
    """
    This method is called when the user makes any change in the GUI.
    The changes are saved into the parameter node (so that they are restored when the scene is saved and loaded).
    """

    if self._parameterNode is None:
      return

    self._parameterNode.SetParameter("InputRoiFilePath", self.ui.inputRoiPathLineEdit.currentPath)
    self._parameterNode.SetNodeReferenceID("OutputSegmentation", self.ui.outputSelector.currentNodeID)

  def onApplyButton(self):
    """
    Run processing when user clicks "Apply" button.
    """

    if not hasattr(slicer.modules, 'dicomrtimportexport'):
      slicer.util.warningDisplay("SlicerRT extension is not installed. Segmentation will be imported as a set of parallel contours. Install SlicerRT extension to create segmentation as a solid object.")

    self.ui.inputRoiPathLineEdit.addCurrentPathToHistory()
    try:
      self.logic.importOsirixRoiFileToSegmentation(self.ui.inputRoiPathLineEdit.currentPath, self._parameterNode.GetNodeReference("OutputSegmentation"))
    except Exception as e:
      slicer.util.errorDisplay("Import failed: "+str(e))
      import traceback
      traceback.print_exc()


#
# ImportOsirixROILogic
#

class ImportOsirixROILogic(ScriptedLoadableModuleLogic):
  """This class should implement all the actual
  computation done by your module.  The interface
  should be such that other python code can import
  this class and make use of the functionality without
  requiring an instance of the Widget.
  Uses ScriptedLoadableModuleLogic base class, available at:
  https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
  """

  def setDefaultParameters(self, parameterNode):
    """
    Initialize parameter node with default settings.
    """
    pass

  def importOsirixRoiFileToSegmentation(self, inputRoi, outputSegmentationNode):
    """
    Run the processing algorithm.
    Can be used without GUI widget.
    :param inputRoi: input OsiriX ROI file path or text node
    :param outputSegmentation: output segmentation node
    """

    if not inputRoi or not outputSegmentationNode:
      raise ValueError("Input file or output segmentation node is invalid")

    logging.info('Processing started')

    inputRoiData = None
    import json
    import plistlib
    if isinstance(inputRoi,str):
      filename, ext = os.path.splitext(inputRoi)
      if ext.lower() == '.json':
        with open(inputRoi) as f:
          inputRoiData = json.load(f)
      else:
        inputRoiData = plistlib.readPlist(inputRoi)
    elif isinstance(inputRoi, slicer.vtkMRMLTextNode):
      try:
        inputRoiData = json.loads(inputRoi.GetText())
      except:
        inputRoiData = plistlib.readPlistFromBytes(inputRoi.GetText())
    else:
      raise TypeError("inputRoi is expected to be a string or vtkMRMLTextNode")

    outputSegmentationNode.CreateDefaultDisplayNodes()
    segmentation = outputSegmentationNode.GetSegmentation()
    segmentation.SetMasterRepresentationName(slicer.vtkSegmentationConverter.GetPlanarContourRepresentationName())

    roiContourPoints = vtk.vtkPoints()
    roiContourCells = vtk.vtkCellArray()

    name = None
    color = [ 1.0, 0.0, 0.0 ]

    if "Images" not in inputRoiData:
      for contour in inputRoiData:
        roiPoints = contour["ROI3DPoints"]
        cellPointIds = []
        for roiPoint in roiPoints:
          roiPointStrList = roiPoint.strip('[]').split(',')
          cellPointIds.append(roiContourPoints.InsertNextPoint(-float(roiPointStrList[0]), -float(roiPointStrList[1]), float(roiPointStrList[2])))
        contourIndex = roiContourCells.InsertNextCell(len(cellPointIds)+1)
        for cellPointId in cellPointIds:
          roiContourCells.InsertCellPoint(cellPointId)
        roiContourCells.InsertCellPoint(cellPointIds[0])  # close the contour
      name = inputRoiJson[0]["Name"]
    else:
      # Output of Export ROIs plugin
      for image in inputRoiData['Images']:
        rois = image['ROIs']
        for roi in rois:
          if not name:
            if roi['Name']:
              name = roi['Name']
          roiPoints = roi['Point_mm']
          cellPointIds = []
          for roiPoint in roiPoints:
            roiPointStrList = roiPoint.strip('()').split(',')
            cellPointIds.append(roiContourPoints.InsertNextPoint(-float(roiPointStrList[0]), -float(roiPointStrList[1]), float(roiPointStrList[2])))
          contourIndex = roiContourCells.InsertNextCell(len(cellPointIds)+1)
          for cellPointId in cellPointIds:
            roiContourCells.InsertCellPoint(cellPointId)
          roiContourCells.InsertCellPoint(cellPointIds[0])  # close the contour

    roiPolyData = vtk.vtkPolyData()
    roiPolyData.SetPoints(roiContourPoints)
    roiPolyData.SetLines(roiContourCells)

    segment = slicer.vtkSegment()
    segment.SetName(name)
    segment.SetColor(color)
    segment.AddRepresentation(slicer.vtkSegmentationConverter.GetPlanarContourRepresentationName(), roiPolyData)

    segmentation.AddSegment(segment)

    outputSegmentationNode.CreateBinaryLabelmapRepresentation()
    outputSegmentationNode.CreateClosedSurfaceRepresentation()
    outputSegmentationNode.GetDisplayNode().SetPreferredDisplayRepresentationName2D(slicer.vtkSegmentationConverter.GetBinaryLabelmapRepresentationName())
    outputSegmentationNode.GetDisplayNode().SetPreferredDisplayRepresentationName3D(slicer.vtkSegmentationConverter.GetClosedSurfaceRepresentationName())

    logging.info('Processing completed')

#
# ImportOsirixROITest
#

class ImportOsirixROITest(ScriptedLoadableModuleTest):
  """
  This is the test case for your scripted module.
  Uses ScriptedLoadableModuleTest base class, available at:
  https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
  """

  def setUp(self):
    """ Do whatever is needed to reset the state - typically a scene clear will be enough.
    """
    slicer.mrmlScene.Clear(0)

  def runTest(self):
    """Run as few or as many tests as needed here.
    """
    self.setUp()
    self.test_ImportOsirixROI1()

  def test_ImportOsirixROI1(self):
    """ Ideally you should have several levels of tests.  At the lowest level
    tests should exercise the functionality of the logic with different inputs
    (both valid and invalid).  At higher levels your tests should emulate the
    way the user would interact with your code and confirm that it still works
    the way you intended.
    One of the most important features of the tests is that it should alert other
    developers when their changes will have an impact on the behavior of your
    module.  For example, if a developer removes a feature that you depend on,
    your test should break so they know that the feature is needed.
    """

    self.delayDisplay("Starting the test")

    # Get/create input data

    import SampleData
    jsonTextNode = SampleData.downloadFromURL(
      nodeNames='OsirixTestROI_1',
      fileNames='OsirixTestROI_1.json',
      loadFileTypes='TextFile',
      uris='https://raw.githubusercontent.com/PerkLab/SlicerSandbox/master/ImportOsirixROI/Testing/Data/OsirixTestROI_1.json',
      checksums='MD5:0ffb20a8802b08884753b5f4c0cb18c9')[0]

    self.delayDisplay('Finished with download and loading')

    outputSegmentation = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLSegmentationNode")

    # Test the module logic
    logic = ImportOsirixROILogic()
    logic.importOsirixRoiFileToSegmentation(jsonTextNode, outputSegmentation)
    self.assertEqual(outputSegmentation.GetSegmentation().GetNumberOfSegments(), 1)
    self.assertEqual(outputSegmentation.GetClosedSurfaceInternalRepresentation("Test").GetNumberOfPoints(), 2935)

    self.delayDisplay('Test passed')

# # ImportOsirixROI # Uses ScriptedLoadableModule base class, available at:   https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py Import Osirix ROI files to Slicer segmentation. # TODO: verify that the default URL is correct or change it to the actual documentation This file was originally developed by <NAME>, PerkLab. # # ImportOsirixROIWidget # Uses ScriptedLoadableModuleWidget base class, available at:   https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py Called when the user opens the module the first time and the widget is initialized. # needed for parameter node observation Called when the user opens the module the first time and the widget is initialized. # Load widget from .ui file (created by Qt Designer) # Set scene in MRML widgets. Make sure that in Qt designer # "mrmlSceneChanged(vtkMRMLScene*)" signal in is connected to each MRML widget's. # "setMRMLScene(vtkMRMLScene*)" slot. # Create a new parameterNode # This parameterNode stores all user choices in parameter values, node selections, etc. # so that when the scene is saved and reloaded, these settings are restored. # Connections # These connections ensure that whenever user changes some settings on the GUI, that is saved in the MRML scene # (in the selected parameter node). # Initial GUI update Called when the application closes and the module widget is destroyed. Adds observers to the selected parameter node. Observation is needed because when the     parameter node is changed then the GUI must be updated immediately. # Set parameter node in the parameter node selector widget # No change # Unobserve previusly selected parameter node and add an observer to the newly selected. # Changes of parameter node are observed so that whenever parameters are changed by a script or any other module # those are reflected immediately in the GUI. # Initial GUI update This method is called whenever parameter node is changed.     The module GUI is updated to show the current state of the parameter node. # Disable all sections if no parameter node is selected # Update each widget from parameter node # Need to temporarily block signals to prevent infinite recursion (MRML node update triggers # GUI update, which triggers MRML node update, which triggers GUI update, ...) # Update buttons states and tooltips This method is called when the user makes any change in the GUI.     The changes are saved into the parameter node (so that they are restored when the scene is saved and loaded). Run processing when user clicks "Apply" button. # # ImportOsirixROILogic # This class should implement all the actual   computation done by your module.  The interface   should be such that other python code can import   this class and make use of the functionality without   requiring an instance of the Widget.   Uses ScriptedLoadableModuleLogic base class, available at:   https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py Initialize parameter node with default settings. Run the processing algorithm.     Can be used without GUI widget.     :param inputRoi: input OsiriX ROI file path or text node     :param outputSegmentation: output segmentation node # close the contour # Output of Export ROIs plugin # close the contour # # ImportOsirixROITest # This is the test case for your scripted module.   Uses ScriptedLoadableModuleTest base class, available at:   https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py Do whatever is needed to reset the state - typically a scene clear will be enough. Run as few or as many tests as needed here. Ideally you should have several levels of tests.  At the lowest level     tests should exercise the functionality of the logic with different inputs     (both valid and invalid).  At higher levels your tests should emulate the     way the user would interact with your code and confirm that it still works     the way you intended.     One of the most important features of the tests is that it should alert other     developers when their changes will have an impact on the behavior of your     module.  For example, if a developer removes a feature that you depend on,     your test should break so they know that the feature is needed. # Get/create input data # Test the module logic