Skip to content

Commit

Permalink
ENH: Manual camera placement
Browse files Browse the repository at this point in the history
  • Loading branch information
NicerNewerCar committed Aug 30, 2023
1 parent f1508d4 commit 713e827
Show file tree
Hide file tree
Showing 4 changed files with 461 additions and 98 deletions.
96 changes: 96 additions & 0 deletions AutoscoperM/AutoscoperM.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -205,6 +206,11 @@ def setup(self):
os.path.join(slicer.mrmlScene.GetCacheManager().GetRemoteCacheDirectory(), "AutoscoperM-Pre-Processing")
)

# Dynamic camera frustum functions
self.ui.mVRG_markupSelector.connect("currentNodeChanged(vtkMRMLNode*)", self.onMarkupNodeChanged)
self.ui.mVRG_ClippingRangeSlider.connect("valuesChanged(double,double)", self.updateClippingRange)
self.ui.mVRG_viewAngleSpin.connect("valueChanged(int)", self.updateViewAngle)

# Make sure parameter node is initialized (needed for module reload)
self.initializeParameterNode()

Expand Down Expand Up @@ -609,6 +615,95 @@ 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.minimumValue, self.ui.mVRG_ClippingRangeSlider.maximumValue)
width = self.ui.vrgRes_width.value
height = self.ui.vrgRes_height.value
vrgDir = self.ui.vrgSubDir.text
cameraDir = self.ui.cameraSubDir.text
if not self.logic.validateInputs(
markupsNode=markupsNode,
volumeNode=volumeNode,
segmentationNode=segmentationNode,
mainOutputDir=mainOutputDir,
viewAngle=viewAngle,
clippingRange=clippingRange,
width=width,
height=height,
vrgDir=vrgDir,
cameraDir=cameraDir,
):
logging.error("Failed to generate VRG: invalid inputs")
return
if not self.logic.validatePaths(mainOutputDir=mainOutputDir):
logging.error("Failed to generate VRG: invalid output directory")
return
self.logic.createPathsIfNotExists(os.path.join(mainOutputDir, vrgDir), os.path.join(mainOutputDir, cameraDir))

if self.logic.vrgManualCameras is None:
self.onMarkupNodeChanged(markupsNode) # create the cameras

volumeImageData, _ = self.logic.extractSubVolumeForVRG(
volumeNode, segmentationNode, cameraDebugMode=self.ui.camDebugCheckbox.isChecked()
)

self.logic.generateVRGForCameras(
self.logic.vrgManualCameras,
volumeImageData,
os.path.join(mainOutputDir, vrgDir),
width,
height,
progressCallback=self.updateProgressBar,
)

self.updateProgressBar(100)

for cam in self.logic.vrgManualCameras:
IO.generateCameraCalibrationFile(cam, os.path.join(mainOutputDir, cameraDir, f"cam{cam.id}.yaml"))

def onMarkupNodeChanged(self, node):
if node is None:
if self.logic.vrgManualCameras is not None:
# clean up
for cam in self.logic.vrgManualCameras:
slicer.mrmlScene.RemoveNode(cam.FrustumModel)
self.logic.vrgManualCameras = None
return
if self.logic.vrgManualCameras is not None:
# clean up
for cam in self.logic.vrgManualCameras:
slicer.mrmlScene.RemoveNode(cam.FrustumModel)
self.logic.vrgManualCameras = None
# get the volume and segmentation nodes
segmentationNode = self.ui.mVRG_segmentationSelector.currentNode()
if not self.logic.validateInputs(segmentationNode=segmentationNode):
return
bounds = [0] * 6
segmentationNode.GetBounds(bounds)
self.logic.vrgManualCameras = RadiographGeneration.generateCamerasFromMarkups(
node,
bounds,
(self.ui.mVRG_ClippingRangeSlider.minimumValue, self.ui.mVRG_ClippingRangeSlider.maximumValue),
self.ui.mVRG_viewAngleSpin.value,
[self.ui.vrgRes_width.value, self.ui.vrgRes_height.value],
True,
)

def updateClippingRange(self, min, max):
for cam in self.logic.vrgManualCameras:
cam.vtkCamera.SetClippingRange(min, max)
RadiographGeneration._updateFrustumModel(cam)

def updateViewAngle(self, value):
for cam in self.logic.vrgManualCameras:
cam.vtkCamera.SetViewAngle(value)
RadiographGeneration._updateFrustumModel(cam)


#
# AutoscoperMLogic
Expand All @@ -634,6 +729,7 @@ def __init__(self):
self.AutoscoperProcess = qt.QProcess()
self.AutoscoperProcess.setProcessChannelMode(qt.QProcess.ForwardedChannels)
self.AutoscoperSocket = None
self.vrgManualCameras = None

def setDefaultParameters(self, parameterNode):
"""
Expand Down
9 changes: 9 additions & 0 deletions AutoscoperM/AutoscoperMLib/IO.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,15 @@ def generateCameraCalibrationFile(camera: Camera, filename: str):
+ str(camera.vtkCamera.GetPosition()[2])
+ "]\n"
)
f.write(
"view-up: ["
+ str(camera.vtkCamera.GetViewUp()[0])
+ ", "
+ str(camera.vtkCamera.GetViewUp()[1])
+ ", "
+ str(camera.vtkCamera.GetViewUp()[2])
+ "]\n"
)
f.write("view-angle: " + str(camera.vtkCamera.GetViewAngle()) + "\n")
f.write("image-width: " + str(camera.imageSize[0]) + "\n")
f.write("image-height: " + str(camera.imageSize[1]) + "\n")
Expand Down
81 changes: 66 additions & 15 deletions AutoscoperM/AutoscoperMLib/RadiographGeneration.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,23 @@ def __str__(self) -> str:


def _createFrustumModel(cam: Camera) -> None:
model = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLModelNode")
model.CreateDefaultDisplayNodes()
model.GetDisplayNode().SetColor(1, 0, 0)
model.GetDisplayNode().SetOpacity(0.3)
model.GetDisplayNode().SetVisibility(True)

model.SetName(f"cam{cam.id}-frustum")

cam.FrustumModel = model

_updateFrustumModel(cam)


def _updateFrustumModel(cam: Camera) -> None:
if cam.FrustumModel is None:
_createFrustumModel(cam)
return
planesArray = [0] * 24
aspectRatio = cam.vtkCamera.GetExplicitAspectRatio()

Expand All @@ -42,18 +59,7 @@ def _createFrustumModel(cam: Camera) -> None:
pd = vtk.vtkPolyData()
hull.GenerateHull(pd, [-1000, 1000, -1000, 1000, -1000, 1000])

# Display the frustum
model = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLModelNode")
model.SetAndObservePolyData(pd)
model.CreateDefaultDisplayNodes()
model.GetDisplayNode().SetColor(1, 0, 0)
model.GetDisplayNode().SetOpacity(0.3)
# Set display to off
model.GetDisplayNode().SetVisibility(False)

model.SetName(f"cam{cam.id}-frustum")

cam.FrustumModel = model
cam.FrustumModel.SetAndObservePolyData(pd)


def generateNCameras(
Expand Down Expand Up @@ -116,10 +122,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)
Expand All @@ -144,6 +148,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,
Expand Down
Loading

0 comments on commit 713e827

Please sign in to comment.