diff --git a/AutoscoperM/AutoscoperM.py b/AutoscoperM/AutoscoperM.py index 6bcb5fc..c132c84 100644 --- a/AutoscoperM/AutoscoperM.py +++ b/AutoscoperM/AutoscoperM.py @@ -195,6 +195,7 @@ def setup(self): # Pre-processing Library Buttons self.ui.tiffGenButton.connect("clicked(bool)", self.onGeneratePartialVolumes) self.ui.vrgGenButton.connect("clicked(bool)", self.onGenerateVRG) + self.ui.manualVRGGenButton.connect("clicked(bool)", self.onManualVRGGen) self.ui.configGenButton.connect("clicked(bool)", self.onGenerateConfig) self.ui.segmentationButton.connect("clicked(bool)", self.onSegmentation) @@ -609,6 +610,59 @@ def onLoadPV(self): volumeNode.SetAndObserveTransformNodeID(transformNode.GetID()) self.logic.showVolumeIn3D(volumeNode) + def onManualVRGGen(self): + markupsNode = self.ui.mVRG_markupSelector.currentNode() + volumeNode = self.ui.volumeSelector.currentNode() + segmentationNode = self.ui.mVRG_segmentationSelector.currentNode() + mainOutputDir = self.ui.mainOutputSelector.currentPath + viewAngle = self.ui.mVRG_viewAngleSpin.value + clippingRange = (self.ui.mVRG_ClippingRangeSlider.minimum, self.ui.mVRG_ClippingRangeSlider.maximum) + width = self.ui.vrgRes_width.value + height = self.ui.vrgRes_height.value + vrgDir = self.ui.vrgSubDir.text + cameraDir = self.ui.cameraSubDir.text + self.logic.validateInputs( + markupsNode=markupsNode, + volumeNode=volumeNode, + segmentationNode=segmentationNode, + mainOutputDir=mainOutputDir, + viewAngle=viewAngle, + clippingRange=clippingRange, + width=width, + height=height, + vrgDir=vrgDir, + cameraDir=cameraDir, + ) + self.logic.validatePaths(mainOutputDir=mainOutputDir) + self.logic.createPathsIfNotExists(os.path.join(mainOutputDir, vrgDir), os.path.join(mainOutputDir, cameraDir)) + + volumeImageData, bounds = self.logic.extractSubVolumeForVRG( + volumeNode, segmentationNode, cameraDebugMode=self.ui.camDebugCheckbox.isChecked() + ) + + cameras = RadiographGeneration.generateCamerasFromMarkups( + markupsNode, + bounds, + clippingRange, + viewAngle, + [width, height], + self.ui.camDebugCheckbox.isChecked(), + ) + + self.logic.generateVRGForCameras( + cameras, + volumeImageData, + os.path.join(mainOutputDir, vrgDir), + width, + height, + progressCallback=self.updateProgressBar, + ) + + self.updateProgressBar(100) + + for cam in cameras: + IO.generateCameraCalibrationFile(cam, os.path.join(mainOutputDir, cameraDir, f"cam{cam.id}.yaml")) + # # AutoscoperMLogic diff --git a/AutoscoperM/AutoscoperMLib/RadiographGeneration.py b/AutoscoperM/AutoscoperMLib/RadiographGeneration.py index 69cd98e..11a0953 100644 --- a/AutoscoperM/AutoscoperMLib/RadiographGeneration.py +++ b/AutoscoperM/AutoscoperMLib/RadiographGeneration.py @@ -116,10 +116,8 @@ def generateNCameras( camera.vtkCamera.SetPosition(points.GetPoint(i)) camera.vtkCamera.SetFocalPoint(center) camera.vtkCamera.SetViewAngle(30) - # Set the far clipping plane to be the distance from the camera to the far side of the volume camera.vtkCamera.SetClippingRange(0.1, r + largestDimension) - # camera.vtkCamera.SetClippingRange(0.1, 1000) - camera.vtkCamera.SetViewUp(0, 1, 0) # Set the view up to be the y axis + camera.vtkCamera.SetViewUp(0, 1, 0) camera.id = i camera.imageSize = imageSize cameras.append(camera) @@ -144,6 +142,53 @@ def generateNCameras( return cameras +def generateCamerasFromMarkups( + fiduaicalNode: slicer.vtkMRMLMarkupsFiducialNode, + volumeBounds: list[int], + clippingRange: tuple[int], + viewAngle: int, + imageSize: tuple[int] = (512, 512), + cameraDebug: bool = False, +) -> list[Camera]: + """ + Generate cameras from a markups fiducial node + + :param fiduaicalNode: Markups fiducial node + :type fiduaicalNode: slicer.vtkMRMLMarkupsFiducialNode + :param volumeBounds: Bounds of the volume + :type volumeBounds: list[int] + :param clippingRange: Clipping range + :type clippingRange: tuple[int] + :param viewAngle: View angle + :type viewAngle: int + :param imageSize: Image size. Defaults to [512,512]. + :type imageSize: list[int] + :param cameraDebug: Whether or not to show the cameras in the scene. Defaults to False. + :type cameraDebug: bool + :return: List of cameras + """ + center = [ + (volumeBounds[0] + volumeBounds[1]) / 2, + (volumeBounds[2] + volumeBounds[3]) / 2, + (volumeBounds[4] + volumeBounds[5]) / 2, + ] + n = fiduaicalNode.GetNumberOfControlPoints() + cameras = [] + for i in range(n): + camera = Camera() + camera.vtkCamera.SetPosition(fiduaicalNode.GetNthControlPointPosition(i)) + camera.vtkCamera.SetFocalPoint(center) + camera.vtkCamera.SetViewAngle(viewAngle) + camera.vtkCamera.SetClippingRange(clippingRange[0], clippingRange[1]) + camera.vtkCamera.SetViewUp(0, 1, 0) + camera.id = fiduaicalNode.GetNthControlPointLabel(i) + camera.imageSize = imageSize + if cameraDebug: + _createFrustumModel(camera) + cameras.append(camera) + return cameras + + def generateVRG( camera: Camera, volumeImageData: vtk.vtkImageData, diff --git a/AutoscoperM/Resources/UI/AutoscoperM.ui b/AutoscoperM/Resources/UI/AutoscoperM.ui index dbcc2dc..3e3f2d4 100644 --- a/AutoscoperM/Resources/UI/AutoscoperM.ui +++ b/AutoscoperM/Resources/UI/AutoscoperM.ui @@ -17,7 +17,7 @@ <item> <widget class="QTabWidget" name="tabWidget"> <property name="currentIndex"> - <number>0</number> + <number>1</number> </property> <widget class="QWidget" name="AutoscoperControlTab"> <attribute name="title"> @@ -266,7 +266,7 @@ <item row="7" column="0"> <widget class="QCheckBox" name="removeVrgTmp"> <property name="enabled"> - <bool>true</bool> + <bool>false</bool> </property> <property name="text"> <string>Delete VRG Temp Subdirecory after optimization</string> @@ -471,13 +471,142 @@ </layout> </widget> </item> + <item> + <widget class="ctkCollapsibleButton" name="CollapsibleButton_2"> + <property name="text"> + <string>VRG Generation - Manual Cmaera Placement</string> + </property> + <layout class="QGridLayout" name="gridLayout_6"> + <item row="2" column="0"> + <widget class="QLabel" name="label_23"> + <property name="text"> + <string>View Angle</string> + </property> + </widget> + </item> + <item row="3" column="2"> + <widget class="ctkDoubleRangeSlider" name="mVRG_ClippingRangeSlider"> + <property name="minimum"> + <double>0.100000000000000</double> + </property> + <property name="maximum"> + <double>2000.000000000000000</double> + </property> + <property name="singleStep"> + <double>0.100000000000000</double> + </property> + <property name="minimumValue"> + <double>0.100000000000000</double> + </property> + <property name="maximumValue"> + <double>800.000000000000000</double> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="label_21"> + <property name="text"> + <string>Segmentation Node</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_22"> + <property name="text"> + <string>Camera Positions</string> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QDoubleSpinBox" name="mVRG_clippingRangeMinBox"> + <property name="minimum"> + <double>0.100000000000000</double> + </property> + <property name="maximum"> + <double>2000.000000000000000</double> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="label_24"> + <property name="text"> + <string>Clipping Range</string> + </property> + </widget> + </item> + <item row="3" column="3"> + <widget class="QDoubleSpinBox" name="mVRG_clippingRangeMaxBox"> + <property name="minimum"> + <double>0.100000000000000</double> + </property> + <property name="maximum"> + <double>2000.000000000000000</double> + </property> + <property name="value"> + <double>800.000000000000000</double> + </property> + </widget> + </item> + <item row="2" column="1" colspan="3"> + <widget class="QSpinBox" name="mVRG_viewAngleSpin"> + <property name="maximum"> + <number>360</number> + </property> + <property name="value"> + <number>30</number> + </property> + </widget> + </item> + <item row="0" column="1" colspan="3"> + <widget class="qMRMLNodeComboBox" name="mVRG_segmentationSelector"> + <property name="nodeTypes"> + <stringlist notr="true"> + <string>vtkMRMLSegmentationNode</string> + </stringlist> + </property> + <property name="hideChildNodeTypes"> + <stringlist notr="true"/> + </property> + <property name="interactionNodeSingletonTag"> + <string notr="true"/> + </property> + </widget> + </item> + <item row="1" column="1" colspan="3"> + <widget class="qMRMLNodeComboBox" name="mVRG_markupSelector"> + <property name="nodeTypes"> + <stringlist notr="true"> + <string>vtkMRMLMarkupsFiducialNode</string> + </stringlist> + </property> + <property name="hideChildNodeTypes"> + <stringlist notr="true"/> + </property> + <property name="interactionNodeSingletonTag"> + <string notr="true"/> + </property> + </widget> + </item> + <item row="4" column="0" colspan="4"> + <widget class="QPushButton" name="manualVRGGenButton"> + <property name="text"> + <string>Generate VRGs from Markups</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> <item> <widget class="ctkCollapsibleButton" name="VRGGen"> <property name="enabled"> <bool>true</bool> </property> <property name="text"> - <string>VRG Generation</string> + <string>VRG Generation - Automatic Camera Placement</string> </property> <property name="checked"> <bool>false</bool> @@ -486,14 +615,20 @@ <bool>true</bool> </property> <layout class="QGridLayout" name="gridLayout_5"> - <item row="5" column="0" colspan="4"> - <widget class="QPushButton" name="vrgGenButton"> - <property name="text"> - <string>Generate VRGs</string> + <item row="3" column="2"> + <widget class="QSpinBox" name="camOffSetSpin"> + <property name="minimum"> + <number>0</number> + </property> + <property name="maximum"> + <number>1000</number> + </property> + <property name="value"> + <number>400</number> </property> </widget> </item> - <item row="1" column="2" colspan="2"> + <item row="1" column="1" colspan="2"> <widget class="QSpinBox" name="posCamSpin"> <property name="minimum"> <number>10</number> @@ -510,20 +645,32 @@ </property> </widget> </item> - <item row="3" column="3"> - <widget class="QSpinBox" name="camOffSetSpin"> - <property name="minimum"> - <number>0</number> + <item row="1" column="0"> + <widget class="QLabel" name="label_7"> + <property name="text"> + <string># of Possible Cameras:</string> </property> - <property name="maximum"> - <number>1000</number> + </widget> + </item> + <item row="2" column="1" colspan="2"> + <widget class="qMRMLNodeComboBox" name="vrg_SegNodeComboBox"> + <property name="enabled"> + <bool>true</bool> </property> - <property name="value"> - <number>400</number> + <property name="nodeTypes"> + <stringlist notr="true"> + <string>vtkMRMLSegmentationNode</string> + </stringlist> + </property> + <property name="hideChildNodeTypes"> + <stringlist notr="true"/> + </property> + <property name="interactionNodeSingletonTag"> + <string notr="true"/> </property> </widget> </item> - <item row="3" column="2"> + <item row="3" column="1"> <widget class="QSlider" name="camOffSetSlider"> <property name="autoFillBackground"> <bool>false</bool> @@ -542,20 +689,17 @@ </property> </widget> </item> - <item row="0" column="2" colspan="2"> - <widget class="QSpinBox" name="optCamSpin"> - <property name="minimum"> - <number>2</number> - </property> - <property name="value"> - <number>2</number> + <item row="5" column="0" colspan="3"> + <widget class="QPushButton" name="vrgGenButton"> + <property name="text"> + <string>Generate VRGs</string> </property> </widget> </item> - <item row="1" column="0"> - <widget class="QLabel" name="label_7"> + <item row="2" column="0"> + <widget class="QLabel" name="label_18"> <property name="text"> - <string># of Possible Cameras:</string> + <string>Segmentation Node:</string> </property> </widget> </item> @@ -566,28 +710,13 @@ </property> </widget> </item> - <item row="2" column="0"> - <widget class="QLabel" name="label_18"> - <property name="text"> - <string>Segmentation Node:</string> - </property> - </widget> - </item> - <item row="2" column="2" colspan="2"> - <widget class="qMRMLNodeComboBox" name="vrg_SegNodeComboBox"> - <property name="enabled"> - <bool>true</bool> - </property> - <property name="nodeTypes"> - <stringlist notr="true"> - <string>vtkMRMLSegmentationNode</string> - </stringlist> - </property> - <property name="hideChildNodeTypes"> - <stringlist notr="true"/> + <item row="0" column="1" colspan="2"> + <widget class="QSpinBox" name="optCamSpin"> + <property name="minimum"> + <number>2</number> </property> - <property name="interactionNodeSingletonTag"> - <string notr="true"/> + <property name="value"> + <number>2</number> </property> </widget> </item> @@ -609,15 +738,15 @@ <bool>true</bool> </property> <layout class="QGridLayout" name="gridLayout_4"> - <item row="0" column="0"> - <widget class="QLabel" name="label_8"> + <item row="1" column="4"> + <widget class="QCheckBox" name="flipY"> <property name="text"> - <string>Optimization Offsets:</string> + <string>Flip Y</string> </property> </widget> </item> - <item row="0" column="2"> - <widget class="QDoubleSpinBox" name="optOffY"> + <item row="0" column="3"> + <widget class="QDoubleSpinBox" name="optOffZ"> <property name="singleStep"> <double>0.100000000000000</double> </property> @@ -633,8 +762,8 @@ </property> </widget> </item> - <item row="0" column="5"> - <widget class="QDoubleSpinBox" name="optOffPitch"> + <item row="0" column="4"> + <widget class="QDoubleSpinBox" name="optOffYaw"> <property name="singleStep"> <double>0.100000000000000</double> </property> @@ -643,8 +772,15 @@ </property> </widget> </item> - <item row="0" column="3"> - <widget class="QDoubleSpinBox" name="optOffZ"> + <item row="1" column="2"> + <widget class="QCheckBox" name="flipX"> + <property name="text"> + <string>Flip X</string> + </property> + </widget> + </item> + <item row="0" column="2"> + <widget class="QDoubleSpinBox" name="optOffY"> <property name="singleStep"> <double>0.100000000000000</double> </property> @@ -653,8 +789,8 @@ </property> </widget> </item> - <item row="0" column="4"> - <widget class="QDoubleSpinBox" name="optOffYaw"> + <item row="0" column="6"> + <widget class="QDoubleSpinBox" name="optOffRoll"> <property name="singleStep"> <double>0.100000000000000</double> </property> @@ -663,6 +799,13 @@ </property> </widget> </item> + <item row="1" column="6"> + <widget class="QCheckBox" name="flipZ"> + <property name="text"> + <string>Flip Z</string> + </property> + </widget> + </item> <item row="0" column="1"> <widget class="QDoubleSpinBox" name="optOffX"> <property name="singleStep"> @@ -673,8 +816,8 @@ </property> </widget> </item> - <item row="0" column="6"> - <widget class="QDoubleSpinBox" name="optOffRoll"> + <item row="0" column="5"> + <widget class="QDoubleSpinBox" name="optOffPitch"> <property name="singleStep"> <double>0.100000000000000</double> </property> @@ -683,31 +826,17 @@ </property> </widget> </item> - <item row="1" column="0"> - <widget class="QLabel" name="label_11"> - <property name="text"> - <string>Volume Flip:</string> - </property> - </widget> - </item> - <item row="1" column="2"> - <widget class="QCheckBox" name="flipX"> - <property name="text"> - <string>Flip X</string> - </property> - </widget> - </item> - <item row="1" column="4"> - <widget class="QCheckBox" name="flipY"> + <item row="0" column="0"> + <widget class="QLabel" name="label_8"> <property name="text"> - <string>Flip Y</string> + <string>Optimization Offsets:</string> </property> </widget> </item> - <item row="1" column="6"> - <widget class="QCheckBox" name="flipZ"> + <item row="1" column="0"> + <widget class="QLabel" name="label_11"> <property name="text"> - <string>Flip Z</string> + <string>Volume Flip:</string> </property> </widget> </item> @@ -757,6 +886,11 @@ <header>ctkCollapsibleGroupBox.h</header> <container>1</container> </customwidget> + <customwidget> + <class>ctkDoubleRangeSlider</class> + <extends>QWidget</extends> + <header>ctkDoubleRangeSlider.h</header> + </customwidget> <customwidget> <class>ctkPathLineEdit</class> <extends>QWidget</extends> @@ -893,5 +1027,69 @@ </hint> </hints> </connection> + <connection> + <sender>AutoscoperM</sender> + <signal>mrmlSceneChanged(vtkMRMLScene*)</signal> + <receiver>mVRG_markupSelector</receiver> + <slot>setMRMLScene(vtkMRMLScene*)</slot> + <hints> + <hint type="sourcelabel"> + <x>508</x> + <y>429</y> + </hint> + <hint type="destinationlabel"> + <x>507</x> + <y>409</y> + </hint> + </hints> + </connection> + <connection> + <sender>AutoscoperM</sender> + <signal>mrmlSceneChanged(vtkMRMLScene*)</signal> + <receiver>mVRG_segmentationSelector</receiver> + <slot>setMRMLScene(vtkMRMLScene*)</slot> + <hints> + <hint type="sourcelabel"> + <x>508</x> + <y>429</y> + </hint> + <hint type="destinationlabel"> + <x>507</x> + <y>409</y> + </hint> + </hints> + </connection> + <connection> + <sender>mVRG_ClippingRangeSlider</sender> + <signal>minimumValueChanged(double)</signal> + <receiver>mVRG_clippingRangeMinBox</receiver> + <slot>setValue(double)</slot> + <hints> + <hint type="sourcelabel"> + <x>567</x> + <y>499</y> + </hint> + <hint type="destinationlabel"> + <x>189</x> + <y>499</y> + </hint> + </hints> + </connection> + <connection> + <sender>mVRG_ClippingRangeSlider</sender> + <signal>maximumValueChanged(double)</signal> + <receiver>mVRG_clippingRangeMaxBox</receiver> + <slot>setValue(double)</slot> + <hints> + <hint type="sourcelabel"> + <x>567</x> + <y>499</y> + </hint> + <hint type="destinationlabel"> + <x>944</x> + <y>499</y> + </hint> + </hints> + </connection> </connections> </ui>