{"action":"created","comment":{"url":"https://api.github.com/repos/Slicer/Slicer/pulls/comments/196638157","pull_request_review_id":130223775,"id":196638157,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDE5NjYzODE1Nw==","diff_hunk":"@@ -26,82 +63,420 @@ def __init__(self, parent):\n # VectorToScalarVolumeWidget\n #\n \n-class VectorToScalarVolumeWidget(ScriptedLoadableModuleWidget):\n+class VectorToScalarVolumeWidget(ScriptedLoadableModuleWidget, VTKObservationMixin):\n+  \"\"\"\n+  The user selected parameters are stored in a parameterNode.\n+  \"\"\"\n+\n+  def __init__(self, parent=None):\n+    ScriptedLoadableModuleWidget.__init__(self, parent)\n+    VTKObservationMixin.__init__(self)\n+    self.logic = None\n+    self._parameterNode = None\n \n   def setup(self):\n     ScriptedLoadableModuleWidget.setup(self)\n \n+    self.logic = VectorToScalarVolumeLogic()\n+    self.setParameterNode(self.logic.getParameterNode())\n+\n     # Collapsible button\n     self.selectionCollapsibleButton = ctk.ctkCollapsibleButton()\n     self.selectionCollapsibleButton.text = \"Selection\"\n     self.layout.addWidget(self.selectionCollapsibleButton)\n \n-    # Layout within the collapsible button\n-    self.formLayout = qt.QFormLayout(self.selectionCollapsibleButton)\n+    # Layout within the \"Selection\" collapsible button\n+    parametersFormLayout = qt.QFormLayout(self.selectionCollapsibleButton)\n \n     #\n     # the volume selectors\n     #\n-    self.inputFrame = qt.QFrame(self.selectionCollapsibleButton)\n-    self.inputFrame.setLayout(qt.QHBoxLayout())\n-    self.formLayout.addWidget(self.inputFrame)\n-    self.inputSelector = qt.QLabel(\"Input Vector Volume: \", self.inputFrame)\n-    self.inputFrame.layout().addWidget(self.inputSelector)\n-    self.inputSelector = slicer.qMRMLNodeComboBox(self.inputFrame)\n+    self.inputSelector = slicer.qMRMLNodeComboBox()\n     self.inputSelector.nodeTypes = [\"vtkMRMLVectorVolumeNode\"]\n     self.inputSelector.addEnabled = False\n     self.inputSelector.removeEnabled = False\n-    self.inputSelector.setMRMLScene( slicer.mrmlScene )\n-    self.inputFrame.layout().addWidget(self.inputSelector)\n-\n-    self.outputFrame = qt.QFrame(self.selectionCollapsibleButton)\n-    self.outputFrame.setLayout(qt.QHBoxLayout())\n-    self.formLayout.addWidget(self.outputFrame)\n-    self.outputSelector = qt.QLabel(\"Output Scalar Volume: \", self.outputFrame)\n-    self.outputFrame.layout().addWidget(self.outputSelector)\n-    self.outputSelector = slicer.qMRMLNodeComboBox(self.outputFrame)\n+    self.inputSelector.setMRMLScene(slicer.mrmlScene)\n+    parametersFormLayout.addRow(\"Input Vector Volume: \", self.inputSelector)\n+\n+    self.outputSelector = slicer.qMRMLNodeComboBox()\n     self.outputSelector.nodeTypes = [\"vtkMRMLScalarVolumeNode\"]\n-    self.outputSelector.setMRMLScene( slicer.mrmlScene )\n+    self.outputSelector.hideChildNodeTypes = [\"vtkMRMLVectorVolumeNode\"]\n+    self.outputSelector.setMRMLScene(slicer.mrmlScene)\n     self.outputSelector.addEnabled = True\n     self.outputSelector.renameEnabled = True\n     self.outputSelector.baseName = \"Scalar Volume\"\n-    self.outputFrame.layout().addWidget(self.outputSelector)\n+    parametersFormLayout.addRow(\"Output Scalar Volume: \", self.outputSelector)\n \n+    #\n+    # Options to extract single components\n+    #\n+    self.conversionMethodWidget = VectorToScalarVolumeConversionMethodWidget()\n+    parametersFormLayout.addRow(\"Conversion Method: \", self.conversionMethodWidget)\n     # Apply button\n     self.applyButton = qt.QPushButton(\"Apply\")\n     self.applyButton.toolTip = \"Run Convert the vector to scalar.\"\n-    self.formLayout.addWidget(self.applyButton)\n-    self.applyButton.connect('clicked(bool)', self.onApply)\n+    parametersFormLayout.addRow(self.applyButton)\n+\n+    # Parameter set selector (inspired by SegmentStatistics.py)\n+    self.parameterNodeSelector = slicer.qMRMLNodeComboBox()\n+    self.parameterNodeSelector.nodeTypes = ((\"vtkMRMLScriptedModuleNode\"), \"\")\n+    self.parameterNodeSelector.addAttribute(\"vtkMRMLScriptedModuleNode\", \"ModuleName\", \"VectorToScalarVolume\")\n+    self.parameterNodeSelector.selectNodeUponCreation = True\n+    self.parameterNodeSelector.addEnabled = True\n+    self.parameterNodeSelector.renameEnabled = True\n+    self.parameterNodeSelector.removeEnabled = True\n+    self.parameterNodeSelector.noneEnabled = False\n+    self.parameterNodeSelector.showHidden = True\n+    self.parameterNodeSelector.showChildNodeTypes = False\n+    self.parameterNodeSelector.baseName = \"VectorToScalarVolume\"\n+    self.parameterNodeSelector.setMRMLScene(slicer.mrmlScene)\n+    self.parameterNodeSelector.toolTip = \"Pick parameter set\"\n+    parametersFormLayout.addRow(\"Parameter set: \", self.parameterNodeSelector)\n \n     # Add vertical spacer\n     self.layout.addStretch(1)\n \n+    # Connections\n+    self.parameterNodeSelector.connect('currentNodeChanged(vtkMRMLNode*)', self.setParameterNode)\n+\n+    # updateParameterNodeFromGui\n+    self.inputSelector.connect(\"currentNodeChanged(vtkMRMLNode*)\", self.updateParameterNodeFromGui)\n+    self.outputSelector.connect(\"currentNodeChanged(vtkMRMLNode*)\", self.updateParameterNodeFromGui)\n+    self.conversionMethodWidget.methodSelectorComboBox.connect('currentIndexChanged(int)', self.updateParameterNodeFromGui)\n+    self.conversionMethodWidget.componentsComboBox.connect('currentIndexChanged(int)', self.updateParameterNodeFromGui)\n+\n+    self.applyButton.connect('clicked(bool)', self.onApply)\n+\n+    self.setDefaultParameters()\n+\n+  def cleanup(self):\n+    self.removeObservers()\n+\n+  def setDefaultParameters(self):\n+    \"\"\"Set computation parameters to their default value\"\"\"\n+    self.conversionMethodWidget.setConversionMethod(VectorToScalarVolumeLogic.LUMINANCE)\n+    self.conversionMethodWidget.componentsComboBox.insertItem(self.conversionMethodWidget.EXTRACT_COMPONENT_NONE, \"None\")\n+\n+\n+  def parameterNode(self):\n+    return self._parameterNode\n+\n+  def setParameterNode(self, inputParameterNode):\n+    if inputParameterNode == self._parameterNode:\n+      return\n+    if self._parameterNode is not None:\n+      self.removeObserver(self._parameterNode, vtk.vtkCommand.ModifiedEvent, self.updateGuiFromMRML)\n+    if inputParameterNode is not None:\n+      self.addObserver(inputParameterNode, vtk.vtkCommand.ModifiedEvent, self.updateGuiFromMRML)\n+    self._parameterNode = inputParameterNode\n+\n+  def inputVolumeNode(self):\n+    return self.inputSelector.currentNode()\n+\n+  def setInputVolumeNode(self, node):\n+    if isinstance(node, basestring):\n+      node = getNode(node)\n+    self.inputSelector.setCurrentNode(node)\n+    self.conversionMethodWidget.setInputVolumeNode(node)\n+\n+  def outputVolumeNode(self):\n+    return self.outputSelector.currentNode()\n+\n+  def setOutputVolumeNode(self, node):\n+    if isinstance(node, basestring):\n+      node = getNode(node)\n+    self.outputSelector.setCurrentNode(node)\n+\n+  def updateGuiFromMRML(self, caller=None, event=None):\n+    \"\"\"\n+    Query all the parameters in the parameterNode,\n+    and update the GUI state accordingly if something has changed.\n+    \"\"\"\n+    self.applyButton.enabled = all([self.inputVolumeNode(), self.outputVolumeNode(), self.parameterNode()])\n+    if self.parameterNode is None:\n+      return\n+    self.setInputVolumeNode(self._parameterNode.GetParameter(\"InputVectorVolume\"))\n+    self.setOutputVolumeNode(self._parameterNode.GetParameter(\"OutputScalarVolume\"))\n+    self.conversionMethodWidget.setConversionMethod(self._parameterNode.GetParameter(\"ConversionMethod\"))\n+    self.conversionMethodWidget.setComponentToExtract(int(self._parameterNode.GetParameter(\"ComponentToExtract\")))\n+\n+  def updateParameterNodeFromGui(self):\n+\n+    self.applyButton.enabled = all([self.inputVolumeNode(), self.outputVolumeNode(), self.parameterNode()])\n+    if self._parameterNode is None:\n+      return\n+\n+    with NodeModify(self._parameterNode):\n+      self._parameterNode.SetParameter(\"InputVectorVolume\", getNodeID(self.inputVolumeNode()))\n+      self._parameterNode.SetParameter(\"OutputScalarVolume\", getNodeID(self.outputVolumeNode()))\n+      self._parameterNode.SetParameter(\"ConversionMethod\", self.conversionMethodWidget.conversionMethod())\n+      self._parameterNode.SetParameter(\"ComponentToExtract\", str(self.conversionMethodWidget.componentToExtract()))\n+\n   def onApply(self):\n-    inputVolume = self.inputSelector.currentNode()\n-    outputVolume = self.outputSelector.currentNode()\n-    # check for input data\n-    if not (inputVolume and outputVolume):\n-      slicer.util.errorDisplay('Input and output volumes are required for conversion', windowTitle='Luminance')\n+\n+    with ScopedQtPropertySetter(self.applyButton, {\"enabled\": False, \"text\": \"Working...\"}):\n+      success = self.logic.run(self.inputVolumeNode(),\n+                               self.outputVolumeNode(),\n+                               self.conversionMethodWidget.conversionMethod(),\n+                               self.conversionMethodWidget.componentToExtract())\n+\n+    # make the output volume appear in all the slice views\n+    if success:\n+      selectionNode = slicer.app.applicationLogic().GetSelectionNode()\n+      selectionNode.SetReferenceActiveVolumeID(self.outputVolumeNode().GetID())\n+      slicer.app.applicationLogic().PropagateVolumeSelection(0)\n+\n+\n+#\n+# VectorToScalarVolumeConversionMethodWidget\n+#\n+\n+class VectorToScalarVolumeConversionMethodWidget(qt.QWidget):\n+  \"\"\"\n+  Widget to interact with conversion parameters.\n+  \"\"\"\n+\n+  EXTRACT_COMPONENT_NONE = -1\n+\n+  def __init__(self, parent=None):\n+    qt.QWidget.__init__(self, parent)\n+    self._inputVolumeNode = None\n+    self._conversionMethod = None\n+    self._componentToExtract = self.EXTRACT_COMPONENT_NONE\n+    self.setup()\n+\n+  def setup(self):\n+    self.methodLayout = qt.QHBoxLayout(self)\n+    self.methodSelectorComboBox = qt.QComboBox()\n+    self.methodSelectorComboBox.insertItem(1, \"Single Component Extraction\", VectorToScalarVolumeLogic.SINGLE_COMPONENT)\n+    self.methodSelectorComboBox.setItemData(1, 'Extract single component', qt.Qt.ToolTipRole)\n+    self.methodSelectorComboBox.insertItem(2, \"Luminance\", VectorToScalarVolumeLogic.LUMINANCE)\n+    self.methodSelectorComboBox.setItemData(2, '(RGB,RGBA) Luminance from first three components: 0.30*R + 0.59*G + 0.11*B + 0.0*A)', qt.Qt.ToolTipRole)\n+    # XXX: Average could be extended to let the user choose the weights of the components.\n+    self.methodSelectorComboBox.insertItem(3, \"Average\", VectorToScalarVolumeLogic.AVERAGE)\n+    self.methodSelectorComboBox.setItemData(3, 'Average all the components.', qt.Qt.ToolTipRole)\n+\n+    self.methodLayout.addWidget(self.methodSelectorComboBox)\n+    self.methodLayout.addItem(qt.QSpacerItem(40, 20, qt.QSizePolicy.Expanding, qt.QSizePolicy.Minimum))\n+\n+    # Option: Single component\n+    singleComponentLayout = qt.QHBoxLayout()\n+    self.componentsComboBox = qt.QComboBox()\n+    singleComponentLayout.addWidget(self.componentsComboBox)\n+    singleComponentLayout.addItem(qt.QSpacerItem(40, 20, qt.QSizePolicy.Expanding, qt.QSizePolicy.Minimum))\n+    self.methodLayout.addLayout(singleComponentLayout)\n+\n+    # Connections\n+    # setConversionMethod uses Qt::UserRole data as input, not index. The connection makes the conversion from the index to data.\n+    self.methodSelectorComboBox.connect('currentIndexChanged(int)',\n+                                        lambda currentIndex:\n+                                        self.setConversionMethod(self.methodSelectorComboBox.itemData(currentIndex))\n+                                        )\n+\n+    self.componentsComboBox.connect('currentIndexChanged(int)', self.setComponentToExtract)\n+\n+  def inputVolumeNode(self):\n+    return self._inputVolumeNode\n+\n+  def setInputVolumeNode(self, node):\n+    if isinstance(node, basestring):\n+      node = getNode(node)\n+    self._inputVolumeNode = node\n+\n+  def componentToExtract(self):\n+    return self._componentToExtract\n+\n+  def setComponentToExtract(self, value):\n+    if value == self._componentToExtract:\n+      return\n+    self._componentToExtract = value\n+    self.componentsComboBox.setCurrentIndex(value)\n+\n+  def conversionMethod(self):\n+    return self._conversionMethod\n+\n+  def setConversionMethod(self, method):\n+    \"\"\"\n+    method is data (Qt::UserRole) associated to the index in the combo box.\n+    \"\"\"\n+    if method not in VectorToScalarVolumeLogic.CONVERSION_METHODS:\n+      logging.warning(\"trying to set a not valid method: %s\" % method)\n       return\n-    # check that data has enough components\n-    inputImage = inputVolume.GetImageData()\n-    if not inputImage or inputImage.GetNumberOfScalarComponents() < 3:\n-      slicer.util.errorDisplay('Input does not have enough components for conversion',\n-                               windowTitle='Vector to Scalar Volume')\n+\n+    if method == self._conversionMethod:\n       return\n-    # run the filter\n-    # - extract the RGB portions\n+\n+    self._conversionMethod = method\n+    methodIndex = self.methodSelectorComboBox.findData(method)\n+    self.methodSelectorComboBox.setCurrentIndex(methodIndex)\n+\n+    self.componentsComboBox.enabled = (method == VectorToScalarVolumeLogic.SINGLE_COMPONENT)\n+    self.componentsComboBox.clear()\n+\n+    if method == VectorToScalarVolumeLogic.SINGLE_COMPONENT:\n+      # Check and display number of components\n+      if self._inputVolumeNode is not None:\n+        imageComponents = self._inputVolumeNode.GetImageData().GetNumberOfScalarComponents()\n+        for comp in range(imageComponents):\n+          self.componentsComboBox.insertItem(comp, str(comp))\n+    else:\n+      # Disable other combo box.\n+      self.componentsComboBox.enabled = False # insertItem(-1, \"None\")\n+\n+\n+#\n+# VectorToScalarVolumeLogic\n+#\n+\n+class VectorToScalarVolumeLogic(ScriptedLoadableModuleLogic):\n+  \"\"\"\n+  Implement the logic to compute the transform from vector to scalar.\n+  It is stateless, with the run function getting inputs and setting outputs.\n+  \"\"\"\n+\n+  LUMINANCE = 'LUMINANCE'\n+  AVERAGE = 'AVERAGE'\n+  SINGLE_COMPONENT = 'SINGLE_COMPONENT'\n+\n+  CONVERSION_METHODS = (LUMINANCE, AVERAGE, SINGLE_COMPONENT)\n+\n+\n+  def __init__(self, parent = None):\n+    ScriptedLoadableModuleLogic.__init__(self, parent)\n+\n+  @staticmethod\n+  def isValidInputOutputData(inputVolumeNode, outputVolumeNode, conversionMethod, componentToExtract):\n+    \"\"\"\n+    Validate parameters using the parameterNode.\n+    \"\"\"\n+    #\n+    # Checking input/output consistency.\n+    #\n+    if not inputVolumeNode:\n+      msg = 'no input volume node defined'\n+      logging.debug(\"isValidInputOutputData failed: %s\" % msg)\n+      return False, msg\n+    if not outputVolumeNode:\n+      msg = 'no output volume node defined'\n+      logging.debug(\"isValidInputOutputData failed: %s\" % msg)\n+      return False, msg\n+    if inputVolumeNode.GetID() == outputVolumeNode.GetID():\n+      msg = 'input and output volume is the same. ' \\\n+            'Create a new volume for output to avoid this error.'\n+      logging.debug(\"isValidInputOutputData failed: %s\" % msg)\n+      return False, msg\n+\n+    #\n+    # Checking based on method selected\n+    #\n+    if conversionMethod not in (VectorToScalarVolumeLogic.SINGLE_COMPONENT,\n+                                VectorToScalarVolumeLogic.LUMINANCE,\n+                                VectorToScalarVolumeLogic.AVERAGE):\n+      msg = 'conversionMethod %s unrecognized.' % conversionMethod\n+      logging.debug(\"isValidInputOutputData failed: %s\" % msg)\n+      return False, msg\n+\n+    inputImage = inputVolumeNode.GetImageData()\n+    nrbOfComponents = inputImage.GetNumberOfScalarComponents()\n+\n+    # SINGLE_COMPONENT: Check that input has enough components for the given componentToExtract\n+    if conversionMethod == VectorToScalarVolumeLogic.SINGLE_COMPONENT:\n+      # componentToExtract is an index with valid values in the range: [0, nrbOfComponents-1]\n+      if not(0 <= componentToExtract < nrbOfComponents):\n+        msg = 'componentToExtract %d is invalid. Image has only %d components.' % (componentToExtract, nrbOfComponents)\n+        logging.debug(\"isValidInputOutputData failed: %s\" % msg)\n+        return False, msg\n+\n+    # LUMINANCE: Check that input vector has at least three components.\n+    if conversionMethod == VectorToScalarVolumeLogic.LUMINANCE:\n+      if nrbOfComponents < 3:\n+        msg = 'input has only %d components but requires ' \\\n+              'at least 3 components for luminance conversion.' % nrbOfComponents\n+        logging.debug(\"isValidInputOutputData failed: %s\" % msg)\n+        return False, msg\n+\n+    return True, None\n+\n+\n+  def run(self, inputVolumeNode, outputVolumeNode, conversionMethod, componentToExtract):","path":"Modules/Scripted/VectorToScalarVolume/VectorToScalarVolume.py","position":430,"original_position":430,"commit_id":"4319889a523183f4659c0f5862dbfde4ba1cf347","original_commit_id":"4319889a523183f4659c0f5862dbfde4ba1cf347","user":{"login":"lassoan","id":307929,"node_id":"MDQ6VXNlcjMwNzkyOQ==","avatar_url":"https://avatars0.githubusercontent.com/u/307929?v=4","gravatar_id":"","url":"https://api.github.com/users/lassoan","html_url":"https://github.com/lassoan","followers_url":"https://api.github.com/users/lassoan/followers","following_url":"https://api.github.com/users/lassoan/following{/other_user}","gists_url":"https://api.github.com/users/lassoan/gists{/gist_id}","starred_url":"https://api.github.com/users/lassoan/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lassoan/subscriptions","organizations_url":"https://api.github.com/users/lassoan/orgs","repos_url":"https://api.github.com/users/lassoan/repos","events_url":"https://api.github.com/users/lassoan/events{/privacy}","received_events_url":"https://api.github.com/users/lassoan/received_events","type":"User","site_admin":false},"body":"If we use a parameter node then the primary method for running the processing should take a parameter node as input.\r\n\r\nIn addition to that method, you may add a convenience method, which takes, inputVolume, outputVolume, conversionMethod, etc., creates a parameter node and calls the primary method with that.","created_at":"2018-06-20T03:22:03Z","updated_at":"2018-06-20T03:29:30Z","html_url":"https://github.com/Slicer/Slicer/pull/967#discussion_r196638157","pull_request_url":"https://api.github.com/repos/Slicer/Slicer/pulls/967","author_association":"CONTRIBUTOR","_links":{"self":{"href":"https://api.github.com/repos/Slicer/Slicer/pulls/comments/196638157"},"html":{"href":"https://github.com/Slicer/Slicer/pull/967#discussion_r196638157"},"pull_request":{"href":"https://api.github.com/repos/Slicer/Slicer/pulls/967"}}},"pull_request":{"url":"https://api.github.com/repos/Slicer/Slicer/pulls/967","id":193454572,"node_id":"MDExOlB1bGxSZXF1ZXN0MTkzNDU0NTcy","html_url":"https://github.com/Slicer/Slicer/pull/967","diff_url":"https://github.com/Slicer/Slicer/pull/967.diff","patch_url":"https://github.com/Slicer/Slicer/pull/967.patch","issue_url":"https://api.github.com/repos/Slicer/Slicer/issues/967","number":967,"state":"open","locked":false,"title":"ENH: VectorToScalar optionally extract single components.","user":{"login":"phcerdan","id":3021667,"node_id":"MDQ6VXNlcjMwMjE2Njc=","avatar_url":"https://avatars1.githubusercontent.com/u/3021667?v=4","gravatar_id":"","url":"https://api.github.com/users/phcerdan","html_url":"https://github.com/phcerdan","followers_url":"https://api.github.com/users/phcerdan/followers","following_url":"https://api.github.com/users/phcerdan/following{/other_user}","gists_url":"https://api.github.com/users/phcerdan/gists{/gist_id}","starred_url":"https://api.github.com/users/phcerdan/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/phcerdan/subscriptions","organizations_url":"https://api.github.com/users/phcerdan/orgs","repos_url":"https://api.github.com/users/phcerdan/repos","events_url":"https://api.github.com/users/phcerdan/events{/privacy}","received_events_url":"https://api.github.com/users/phcerdan/received_events","type":"User","site_admin":false},"body":"This commit improves the user interface adding support for choosing\r\nthe conversion mode.\r\n\r\nAvailable conversion modes are now:\r\n * vector (RGB, RGBA) images to luminance (default mode keeping backward\r\n        compatibility with original module implementation)\r\n * vector images to single component (user select which component to extract)\r\n\r\nTo support reusing of components by other scripted modules, a logic\r\nand widget have also been created:\r\n * VectorToScalarVolumeConversionModeWidget\r\n * VectorToScalarVolumeLogic\r\n\r\nCo-authored-by: Jean-Christophe Fillion-Robin <jchris.fillionr@kitware.com>","created_at":"2018-06-07T21:37:34Z","updated_at":"2018-06-20T03:29:30Z","closed_at":null,"merged_at":null,"merge_commit_sha":"93ab2418bc3b880d4886ac837888bee1b4bc58f3","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/Slicer/Slicer/pulls/967/commits","review_comments_url":"https://api.github.com/repos/Slicer/Slicer/pulls/967/comments","review_comment_url":"https://api.github.com/repos/Slicer/Slicer/pulls/comments{/number}","comments_url":"https://api.github.com/repos/Slicer/Slicer/issues/967/comments","statuses_url":"https://api.github.com/repos/Slicer/Slicer/statuses/4319889a523183f4659c0f5862dbfde4ba1cf347","head":{"label":"phcerdan:enhance_vector_to_scalar","ref":"enhance_vector_to_scalar","sha":"4319889a523183f4659c0f5862dbfde4ba1cf347","user":{"login":"phcerdan","id":3021667,"node_id":"MDQ6VXNlcjMwMjE2Njc=","avatar_url":"https://avatars1.githubusercontent.com/u/3021667?v=4","gravatar_id":"","url":"https://api.github.com/users/phcerdan","html_url":"https://github.com/phcerdan","followers_url":"https://api.github.com/users/phcerdan/followers","following_url":"https://api.github.com/users/phcerdan/following{/other_user}","gists_url":"https://api.github.com/users/phcerdan/gists{/gist_id}","starred_url":"https://api.github.com/users/phcerdan/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/phcerdan/subscriptions","organizations_url":"https://api.github.com/users/phcerdan/orgs","repos_url":"https://api.github.com/users/phcerdan/repos","events_url":"https://api.github.com/users/phcerdan/events{/privacy}","received_events_url":"https://api.github.com/users/phcerdan/received_events","type":"User","site_admin":false},"repo":{"id":133688169,"node_id":"MDEwOlJlcG9zaXRvcnkxMzM2ODgxNjk=","name":"Slicer","full_name":"phcerdan/Slicer","owner":{"login":"phcerdan","id":3021667,"node_id":"MDQ6VXNlcjMwMjE2Njc=","avatar_url":"https://avatars1.githubusercontent.com/u/3021667?v=4","gravatar_id":"","url":"https://api.github.com/users/phcerdan","html_url":"https://github.com/phcerdan","followers_url":"https://api.github.com/users/phcerdan/followers","following_url":"https://api.github.com/users/phcerdan/following{/other_user}","gists_url":"https://api.github.com/users/phcerdan/gists{/gist_id}","starred_url":"https://api.github.com/users/phcerdan/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/phcerdan/subscriptions","organizations_url":"https://api.github.com/users/phcerdan/orgs","repos_url":"https://api.github.com/users/phcerdan/repos","events_url":"https://api.github.com/users/phcerdan/events{/privacy}","received_events_url":"https://api.github.com/users/phcerdan/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/phcerdan/Slicer","description":"Multi-platform, free open source software for visualization and image computing.","fork":true,"url":"https://api.github.com/repos/phcerdan/Slicer","forks_url":"https://api.github.com/repos/phcerdan/Slicer/forks","keys_url":"https://api.github.com/repos/phcerdan/Slicer/keys{/key_id}","collaborators_url":"https://api.github.com/repos/phcerdan/Slicer/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/phcerdan/Slicer/teams","hooks_url":"https://api.github.com/repos/phcerdan/Slicer/hooks","issue_events_url":"https://api.github.com/repos/phcerdan/Slicer/issues/events{/number}","events_url":"https://api.github.com/repos/phcerdan/Slicer/events","assignees_url":"https://api.github.com/repos/phcerdan/Slicer/assignees{/user}","branches_url":"https://api.github.com/repos/phcerdan/Slicer/branches{/branch}","tags_url":"https://api.github.com/repos/phcerdan/Slicer/tags","blobs_url":"https://api.github.com/repos/phcerdan/Slicer/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/phcerdan/Slicer/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/phcerdan/Slicer/git/refs{/sha}","trees_url":"https://api.github.com/repos/phcerdan/Slicer/git/trees{/sha}","statuses_url":"https://api.github.com/repos/phcerdan/Slicer/statuses/{sha}","languages_url":"https://api.github.com/repos/phcerdan/Slicer/languages","stargazers_url":"https://api.github.com/repos/phcerdan/Slicer/stargazers","contributors_url":"https://api.github.com/repos/phcerdan/Slicer/contributors","subscribers_url":"https://api.github.com/repos/phcerdan/Slicer/subscribers","subscription_url":"https://api.github.com/repos/phcerdan/Slicer/subscription","commits_url":"https://api.github.com/repos/phcerdan/Slicer/commits{/sha}","git_commits_url":"https://api.github.com/repos/phcerdan/Slicer/git/commits{/sha}","comments_url":"https://api.github.com/repos/phcerdan/Slicer/comments{/number}","issue_comment_url":"https://api.github.com/repos/phcerdan/Slicer/issues/comments{/number}","contents_url":"https://api.github.com/repos/phcerdan/Slicer/contents/{+path}","compare_url":"https://api.github.com/repos/phcerdan/Slicer/compare/{base}...{head}","merges_url":"https://api.github.com/repos/phcerdan/Slicer/merges","archive_url":"https://api.github.com/repos/phcerdan/Slicer/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/phcerdan/Slicer/downloads","issues_url":"https://api.github.com/repos/phcerdan/Slicer/issues{/number}","pulls_url":"https://api.github.com/repos/phcerdan/Slicer/pulls{/number}","milestones_url":"https://api.github.com/repos/phcerdan/Slicer/milestones{/number}","notifications_url":"https://api.github.com/repos/phcerdan/Slicer/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/phcerdan/Slicer/labels{/name}","releases_url":"https://api.github.com/repos/phcerdan/Slicer/releases{/id}","deployments_url":"https://api.github.com/repos/phcerdan/Slicer/deployments","created_at":"2018-05-16T15:37:39Z","updated_at":"2018-06-18T14:49:00Z","pushed_at":"2018-06-18T16:28:20Z","git_url":"git://github.com/phcerdan/Slicer.git","ssh_url":"git@github.com:phcerdan/Slicer.git","clone_url":"https://github.com/phcerdan/Slicer.git","svn_url":"https://github.com/phcerdan/Slicer","homepage":"http://www.slicer.org","size":440561,"stargazers_count":0,"watchers_count":0,"language":"C++","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"open_issues_count":0,"license":{"key":"other","name":"Other","spdx_id":null,"url":null,"node_id":"MDc6TGljZW5zZTA="},"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"Slicer:master","ref":"master","sha":"e0bf6ec2be2ae26f8bc05c2a8ef9031340c5ed87","user":{"login":"Slicer","id":324362,"node_id":"MDEyOk9yZ2FuaXphdGlvbjMyNDM2Mg==","avatar_url":"https://avatars0.githubusercontent.com/u/324362?v=4","gravatar_id":"","url":"https://api.github.com/users/Slicer","html_url":"https://github.com/Slicer","followers_url":"https://api.github.com/users/Slicer/followers","following_url":"https://api.github.com/users/Slicer/following{/other_user}","gists_url":"https://api.github.com/users/Slicer/gists{/gist_id}","starred_url":"https://api.github.com/users/Slicer/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Slicer/subscriptions","organizations_url":"https://api.github.com/users/Slicer/orgs","repos_url":"https://api.github.com/users/Slicer/repos","events_url":"https://api.github.com/users/Slicer/events{/privacy}","received_events_url":"https://api.github.com/users/Slicer/received_events","type":"Organization","site_admin":false},"repo":{"id":2006302,"node_id":"MDEwOlJlcG9zaXRvcnkyMDA2MzAy","name":"Slicer","full_name":"Slicer/Slicer","owner":{"login":"Slicer","id":324362,"node_id":"MDEyOk9yZ2FuaXphdGlvbjMyNDM2Mg==","avatar_url":"https://avatars0.githubusercontent.com/u/324362?v=4","gravatar_id":"","url":"https://api.github.com/users/Slicer","html_url":"https://github.com/Slicer","followers_url":"https://api.github.com/users/Slicer/followers","following_url":"https://api.github.com/users/Slicer/following{/other_user}","gists_url":"https://api.github.com/users/Slicer/gists{/gist_id}","starred_url":"https://api.github.com/users/Slicer/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Slicer/subscriptions","organizations_url":"https://api.github.com/users/Slicer/orgs","repos_url":"https://api.github.com/users/Slicer/repos","events_url":"https://api.github.com/users/Slicer/events{/privacy}","received_events_url":"https://api.github.com/users/Slicer/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/Slicer/Slicer","description":"Multi-platform, free open source software for visualization and image computing.","fork":false,"url":"https://api.github.com/repos/Slicer/Slicer","forks_url":"https://api.github.com/repos/Slicer/Slicer/forks","keys_url":"https://api.github.com/repos/Slicer/Slicer/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Slicer/Slicer/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Slicer/Slicer/teams","hooks_url":"https://api.github.com/repos/Slicer/Slicer/hooks","issue_events_url":"https://api.github.com/repos/Slicer/Slicer/issues/events{/number}","events_url":"https://api.github.com/repos/Slicer/Slicer/events","assignees_url":"https://api.github.com/repos/Slicer/Slicer/assignees{/user}","branches_url":"https://api.github.com/repos/Slicer/Slicer/branches{/branch}","tags_url":"https://api.github.com/repos/Slicer/Slicer/tags","blobs_url":"https://api.github.com/repos/Slicer/Slicer/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Slicer/Slicer/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Slicer/Slicer/git/refs{/sha}","trees_url":"https://api.github.com/repos/Slicer/Slicer/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Slicer/Slicer/statuses/{sha}","languages_url":"https://api.github.com/repos/Slicer/Slicer/languages","stargazers_url":"https://api.github.com/repos/Slicer/Slicer/stargazers","contributors_url":"https://api.github.com/repos/Slicer/Slicer/contributors","subscribers_url":"https://api.github.com/repos/Slicer/Slicer/subscribers","subscription_url":"https://api.github.com/repos/Slicer/Slicer/subscription","commits_url":"https://api.github.com/repos/Slicer/Slicer/commits{/sha}","git_commits_url":"https://api.github.com/repos/Slicer/Slicer/git/commits{/sha}","comments_url":"https://api.github.com/repos/Slicer/Slicer/comments{/number}","issue_comment_url":"https://api.github.com/repos/Slicer/Slicer/issues/comments{/number}","contents_url":"https://api.github.com/repos/Slicer/Slicer/contents/{+path}","compare_url":"https://api.github.com/repos/Slicer/Slicer/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Slicer/Slicer/merges","archive_url":"https://api.github.com/repos/Slicer/Slicer/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Slicer/Slicer/downloads","issues_url":"https://api.github.com/repos/Slicer/Slicer/issues{/number}","pulls_url":"https://api.github.com/repos/Slicer/Slicer/pulls{/number}","milestones_url":"https://api.github.com/repos/Slicer/Slicer/milestones{/number}","notifications_url":"https://api.github.com/repos/Slicer/Slicer/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Slicer/Slicer/labels{/name}","releases_url":"https://api.github.com/repos/Slicer/Slicer/releases{/id}","deployments_url":"https://api.github.com/repos/Slicer/Slicer/deployments","created_at":"2011-07-06T12:27:18Z","updated_at":"2018-06-19T20:56:15Z","pushed_at":"2018-06-19T20:56:08Z","git_url":"git://github.com/Slicer/Slicer.git","ssh_url":"git@github.com:Slicer/Slicer.git","clone_url":"https://github.com/Slicer/Slicer.git","svn_url":"https://github.com/Slicer/Slicer","homepage":"http://www.slicer.org","size":441068,"stargazers_count":531,"watchers_count":531,"language":"C++","has_issues":false,"has_projects":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":371,"mirror_url":null,"archived":false,"open_issues_count":23,"license":{"key":"other","name":"Other","spdx_id":null,"url":null,"node_id":"MDc6TGljZW5zZTA="},"forks":371,"open_issues":23,"watchers":531,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/Slicer/Slicer/pulls/967"},"html":{"href":"https://github.com/Slicer/Slicer/pull/967"},"issue":{"href":"https://api.github.com/repos/Slicer/Slicer/issues/967"},"comments":{"href":"https://api.github.com/repos/Slicer/Slicer/issues/967/comments"},"review_comments":{"href":"https://api.github.com/repos/Slicer/Slicer/pulls/967/comments"},"review_comment":{"href":"https://api.github.com/repos/Slicer/Slicer/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/Slicer/Slicer/pulls/967/commits"},"statuses":{"href":"https://api.github.com/repos/Slicer/Slicer/statuses/4319889a523183f4659c0f5862dbfde4ba1cf347"}},"author_association":"NONE"}}
{"actor":{"display_login":"lassoan"}}