Commit c830616f authored by Emmanuel Promayon's avatar Emmanuel Promayon

FIXED plane now always visible + code optimization/simplification

The picked plane is now always visible in all kind of orientations.
All unnecessary attributes were removed to simplify and optimize the code.
The picked plane and pixel actors are not re-instanciated at every picking,
their position us just updated.
parent fbfcc3b5
......@@ -57,45 +57,23 @@ Slice::Slice(vtkSmartPointer<vtkImageData> volume, SliceOrientation orientation,
// -------------------- Destructor --------------------
Slice::~Slice() {
// Let's unreference vtkSmartPointers
originalVolume = nullptr;
lut = nullptr;
imgToMapFilter = nullptr;
image3DActor = nullptr;
image2DActor = nullptr;
pickPlane = nullptr;
pickPlaneActor = nullptr;
pickPlaneMapper = nullptr;
pixelActor = nullptr;
pixelActorPointSet = nullptr;
init();
}
// -------------------- init --------------------
void Slice::init() {
currentSliceIndex = 0;
extent[0] = 0;
extent[1] = 0;
extent[2] = 0;
extent[3] = 0;
extent[4] = 0;
extent[5] = 0;
originalDimensions[0] = -1;
originalDimensions[1] = -1;
originalDimensions[2] = -1;
originalSpacing[0] = -1.0;
originalSpacing[1] = -1.0;
originalSpacing[2] = -1.0;
originalVolume = nullptr;
imgToMapFilter = nullptr;
image3DActor = nullptr;
image2DActor = nullptr;
pickPlane = nullptr;
pickPlaneMapper = nullptr;
pickPlaneActor = nullptr;
pixelActor = nullptr;
for (int i=0; i<3; i++) {
originalSpacing[i] = -1.0;
}
originalVolume = nullptr;
image3DActor = nullptr;
image2DActor = nullptr;
pickPlaneActor = nullptr;
pickPlaneActorPointSet = nullptr;
pixelActor = nullptr;
pixelActorPointSet = nullptr;
}
......@@ -112,28 +90,22 @@ void Slice::setOriginalVolume(vtkSmartPointer<vtkImageData> volume) {
originalVolume = volume;
// Get volume information
/** Original volume dimensions in number of voxels (x, y and z) */
int originalDimensions[3];
originalVolume->GetDimensions(originalDimensions);
originalVolume->GetSpacing(originalSpacing);
originalVolume->GetExtent(extent);
originalSize[0] = originalDimensions[0] * originalSpacing[0];
originalSize[1] = originalDimensions[1] * originalSpacing[1];
originalSize[2] = originalDimensions[2] * originalSpacing[2];
// In other methods, there will be divisions by originalSpacing[k]
// so let's avoid division by 0
if (originalSpacing[0] == 0.0) {
originalSpacing[0] = -1.0;
}
if (originalSpacing[1] == 0.0) {
originalSpacing[1] = -1.0;
}
if (originalSpacing[2] == 0.0) {
originalSpacing[2] = -1.0;
for (int i=0; i<3; i++) {
// compute original size (nb of slice * spacing)
originalSize[i] = originalDimensions[i] * originalSpacing[i];
// As originalSpacing[i] will be used to get the slice number
// for 2D images, lets replace 0.0 by -1.0 to avoid division by 0
if (originalSpacing[i] == 0.0) {
originalSpacing[i] = -1.0;
}
}
// Prepare all the visualization pipeline
initActors();
}
......@@ -164,11 +136,14 @@ void Slice::volumeToReslicedCoords(const double* xyz, double* ijk) {
// -------------------- getNumberOfSlices --------------------
int Slice::getNumberOfSlices() const {
int nbSlices = 0;
// Array containing first and last indices of the image in each direction
// 0 & 1 -> x; 2 and 3 -> y; 4 & 5 -> z
int extent[6];
originalVolume->GetExtent(extent);
int nbSlices;
switch (sliceOrientation) {
default:
break;
case AXIAL:
case AXIAL_NEURO:
nbSlices = extent[5] - extent[4] + 1;
......@@ -179,6 +154,9 @@ int Slice::getNumberOfSlices() const {
case SAGITTAL:
nbSlices = extent[1] - extent[0] + 1;
break;
default:
nbSlices = 0;
break;
}
return nbSlices;
......@@ -204,6 +182,11 @@ void Slice::setSlice(int s) {
}
}
// Array containing first and last indices of the image in each direction
// 0 & 1 -> x; 2 and 3 -> y; 4 & 5 -> z
int extent[6];
originalVolume->GetExtent(extent);
switch (sliceOrientation) {
case AXIAL:
case AXIAL_NEURO:
......@@ -254,6 +237,9 @@ void Slice::setSlice(double x, double y, double z) {
// Set pixel position in current slice.
setPixelRealPosition(x, y, z);
// show the pixel actor
pixelActor->VisibilityOn();
}
// -------------------- getNumberOfColors --------------------
......@@ -264,7 +250,6 @@ int Slice::getNumberOfColors() const {
// -------------------- setPixelRealPosition --------------------
void Slice::setPixelRealPosition(double x, double y, double z) {
updatePixelActor(x, y, z);
pixelActor->VisibilityOn();
}
......@@ -291,11 +276,11 @@ vtkSmartPointer<vtkActor> Slice::getPickPlaneActor() const {
// -------------------- initActors --------------------
void Slice::initActors() {
// Image mapper
imgToMapFilter = vtkSmartPointer<vtkImageMapToColors>::New();
vtkSmartPointer<vtkImageMapToColors> imgToMapFilter = vtkSmartPointer<vtkImageMapToColors>::New();
imgToMapFilter->SetInputData(originalVolume);
// set the lookupTable
imgToMapFilter->SetLookupTable(nullptr);
imgToMapFilter->SetLookupTable(lut);
// the 2D and 3D image actors are directly plugged to the output of imgToMapFilter
image3DActor = vtkSmartPointer<vtkImageActor>::New();
......@@ -317,34 +302,90 @@ void Slice::initActors() {
// -------------------- initPickPlaneActor --------------------
void Slice::initPickPlaneActor() {
pickPlane = vtkSmartPointer<vtkPlaneSource>::New();
// create the pixel actor 3D geometry
// Create the 8 points that describes the plane.
// The points are set at the coordinates of the (red / blue / green) border of the current image plane.
// To make sure that the borders are visible in any specific orientation,
// for quads are build. Each quad goes out of the image plane on both side
// (hit "t" on the slice viewer and move the camera around to see the quad geometries)
vtkSmartPointer<vtkPoints> planePoints = vtkSmartPointer<vtkPoints>::New();
planePoints->SetNumberOfPoints(8);
pickPlaneMapper = vtkSmartPointer<vtkPolyDataMapper>::New();
pickPlaneMapper->SetInputConnection(pickPlane->GetOutputPort());
// create zero-positionned points
for (int i = 0; i < planePoints->GetNumberOfPoints(); i++) {
planePoints->InsertPoint(i, 0.0, 0.0, 0.0);
}
// vtkQuad is defined by the four points (0,1,2,3) in counterclockwise order.
vtkSmartPointer<vtkQuad> leftQuad = vtkSmartPointer<vtkQuad>::New();
leftQuad->GetPointIds()->SetId(0, 0);
leftQuad->GetPointIds()->SetId(1, 1);
leftQuad->GetPointIds()->SetId(2, 2);
leftQuad->GetPointIds()->SetId(3, 3);
vtkSmartPointer<vtkQuad> topQuad = vtkSmartPointer<vtkQuad>::New();
topQuad->GetPointIds()->SetId(0, 4);
topQuad->GetPointIds()->SetId(1, 5);
topQuad->GetPointIds()->SetId(2, 1);
topQuad->GetPointIds()->SetId(3, 0);
vtkSmartPointer<vtkQuad> rightQuad = vtkSmartPointer<vtkQuad>::New();
rightQuad->GetPointIds()->SetId(0, 5);
rightQuad->GetPointIds()->SetId(1, 4);
rightQuad->GetPointIds()->SetId(2, 7);
rightQuad->GetPointIds()->SetId(3, 6);
vtkSmartPointer<vtkQuad> bottomQuad = vtkSmartPointer<vtkQuad>::New();
bottomQuad->GetPointIds()->SetId(0, 3);
bottomQuad->GetPointIds()->SetId(1, 2);
bottomQuad->GetPointIds()->SetId(2, 6);
bottomQuad->GetPointIds()->SetId(3, 7);
// Create the unstructured grid that includes the two quads
pickPlaneActorPointSet = vtkSmartPointer<vtkUnstructuredGrid>::New();
pickPlaneActorPointSet->Allocate(4);
pickPlaneActorPointSet->InsertNextCell(leftQuad->GetCellType(), leftQuad->GetPointIds());
pickPlaneActorPointSet->InsertNextCell(topQuad->GetCellType(), topQuad->GetPointIds());
pickPlaneActorPointSet->InsertNextCell(rightQuad->GetCellType(), rightQuad->GetPointIds());
pickPlaneActorPointSet->InsertNextCell(bottomQuad->GetCellType(), bottomQuad->GetPointIds());
pickPlaneActorPointSet->SetPoints(planePoints);
// Create the corresponding mapper
vtkSmartPointer<vtkDataSetMapper> pickPlaneMapper = vtkSmartPointer<vtkDataSetMapper>::New();
pickPlaneMapper->SetInputData(pickPlaneActorPointSet);
// instantiate the actor
pickPlaneActor = vtkSmartPointer<vtkActor>::New();
pickPlaneActor->SetMapper(pickPlaneMapper);
pickPlaneActor->GetProperty()->SetRepresentationToWireframe();
pickPlaneActor->GetProperty()->SetAmbient(1.0);
pickPlaneActor->GetProperty()->SetDiffuse(1.0);
// Update pixel actor properties
switch (sliceOrientation) {
default:
case AXIAL_NEURO:
pickPlaneActor->GetProperty()->SetColor(1.0, 1.0, 0.0);
break;
case AXIAL:
pickPlaneActor->GetProperty()->SetColor(0.0, 0.0, 1.0);
pickPlaneActor->GetProperty()->SetEdgeColor(0.0, 0.0, 1.0);
break;
case CORONAL:
pickPlaneActor->GetProperty()->SetColor(0.0, 1.0, 0.0);
pickPlaneActor->GetProperty()->SetEdgeColor(0.0, 1.0, 0.0);
break;
case SAGITTAL:
pickPlaneActor->GetProperty()->SetColor(1.0, 0.0, 0.0);
pickPlaneActor->GetProperty()->SetEdgeColor(1.0, 0.0, 0.0);
break;
default:
pickPlaneActor->GetProperty()->SetColor(1.0, 1.0, 1.0);
break;
}
pickPlaneActor->GetProperty()->SetLineWidth(1.0);
pickPlaneActor->GetProperty()->SetRepresentationToWireframe();
pickPlaneActor->GetProperty()->SetOpacity(1.0);
pickPlaneActor->GetProperty()->SetLineWidth(2.0);
//-- pickPlaneActor can not be picked
pickPlaneActor->PickableOff();
// by default, the plane actor is always visible
pickPlaneActor->VisibilityOn();
}
// -------------------- initPixelActor --------------------
......@@ -353,7 +394,7 @@ void Slice::initPixelActor() {
// Create the 8 points that describes the cross centered on the currently picked pixel
// The points are set at the coordinates of the (red / blue / green) cross around the picked pixel
// to make sure that the cross is visible in any specific orientation,
// two quads are build. Each quad that go out of the image plane on both side
// two quads are build. Each quad goes out of the image plane on both side
// (hit "t" on the slice viewer and move the camera around to see the quad geometries)
vtkSmartPointer<vtkPoints> pixelPoints = vtkSmartPointer<vtkPoints>::New();
pixelPoints->SetNumberOfPoints(8);
......@@ -378,18 +419,18 @@ void Slice::initPixelActor() {
// Create the unstructured grid that includes the two quads
pixelActorPointSet = vtkSmartPointer<vtkUnstructuredGrid>::New();
pixelActorPointSet->Allocate(4, 4);
pixelActorPointSet->Allocate(2);
pixelActorPointSet->InsertNextCell(horizontalQuad->GetCellType(), horizontalQuad->GetPointIds());
pixelActorPointSet->InsertNextCell(verticalQuad->GetCellType(), verticalQuad->GetPointIds());
pixelActorPointSet->SetPoints(pixelPoints);
// Create the corresponding mapper
vtkSmartPointer<vtkDataSetMapper> aPixelMapper = vtkSmartPointer<vtkDataSetMapper>::New();
aPixelMapper->SetInputData(pixelActorPointSet);
vtkSmartPointer<vtkDataSetMapper> pixelMapper = vtkSmartPointer<vtkDataSetMapper>::New();
pixelMapper->SetInputData(pixelActorPointSet);
// instantiate the actor
pixelActor = vtkSmartPointer<vtkActor>::New();
pixelActor->SetMapper(aPixelMapper);
pixelActor->SetMapper(pixelMapper);
pixelActor->GetProperty()->SetAmbient(1.0);
pixelActor->GetProperty()->SetDiffuse(1.0);
......@@ -417,9 +458,6 @@ void Slice::initPixelActor() {
//-- pixelActor can not be picked
pixelActor->PickableOff();
// Draw the bounding box around the center of the slice
updatePixelActor();
// by default, the pixel actor is not visible (it should be only visible when user Ctrl-Click on the image)
pixelActor->VisibilityOff();
}
......@@ -428,41 +466,55 @@ void Slice::initPixelActor() {
void Slice::updatePickPlane() {
// Be careful, the center of the first voxel (0,0,0) is displayed at coordinates (0.0, 0.0, 0.0).
// Pixels within borders are represented (in 2D) only by their half, quarter or eigth depending on their coordinates.
// see bug #65 "Unexpected image behaviors in Viewers"
double planeXCoord, planeYCoord, planeZCoord;
// The out of plane shift depending on the orientation
// As the camera is facing the slice from one direction or another depending on the orientation,
// the extra decoration (pixel actor and plane actor) should be shifted a little bit towards
// the camera to guarantee their visibility without any depth/z-buffer problems.
// Use the slice thickness to make sure the plane is visible from the camera.
// see also bug #65 "Unexpected image behaviors in Viewers"
// update the point positions
double sliceBackPlane, sliceFrontPlane;
switch (sliceOrientation) {
case AXIAL_NEURO:
case AXIAL:
planeZCoord = currentSliceIndex - originalSpacing[2];
pickPlane->SetOrigin(0.0, 0.0, planeZCoord);
pickPlane->SetPoint1(originalSize[0], 0.0, planeZCoord);
pickPlane->SetPoint2(0.0, originalSize[1], planeZCoord);
sliceBackPlane = currentSliceIndex*originalSpacing[2] - originalSpacing[2] / 2.0;
sliceFrontPlane = currentSliceIndex*originalSpacing[2] + originalSpacing[2] / 2.0;
pickPlaneActorPointSet->GetPoints()->SetPoint(0, 0.0, 0.0, sliceBackPlane);
pickPlaneActorPointSet->GetPoints()->SetPoint(1, 0.0, 0.0, sliceFrontPlane);
pickPlaneActorPointSet->GetPoints()->SetPoint(2, 0.0, originalSize[1], sliceFrontPlane);
pickPlaneActorPointSet->GetPoints()->SetPoint(3, 0.0, originalSize[1], sliceBackPlane);
pickPlaneActorPointSet->GetPoints()->SetPoint(4, originalSize[0], 0.0, sliceBackPlane);
pickPlaneActorPointSet->GetPoints()->SetPoint(5, originalSize[0], 0.0, sliceFrontPlane);
pickPlaneActorPointSet->GetPoints()->SetPoint(6, originalSize[0], originalSize[1], sliceFrontPlane);
pickPlaneActorPointSet->GetPoints()->SetPoint(7, originalSize[0], originalSize[1], sliceBackPlane);
break;
case CORONAL:
planeYCoord = currentSliceIndex - originalSpacing[1];
pickPlane->SetOrigin(0.0, planeYCoord, 0.0);
pickPlane->SetPoint1(originalSize[0], planeYCoord, 0.0);
pickPlane->SetPoint2(0.0, planeYCoord, originalSize[2]);
sliceBackPlane = currentSliceIndex*originalSpacing[1] - originalSpacing[1] / 2.0;
sliceFrontPlane = currentSliceIndex*originalSpacing[1] + originalSpacing[1] / 2.0;
pickPlaneActorPointSet->GetPoints()->SetPoint(0, 0.0, sliceBackPlane, 0.0);
pickPlaneActorPointSet->GetPoints()->SetPoint(1, 0.0, sliceFrontPlane, 0.0);
pickPlaneActorPointSet->GetPoints()->SetPoint(2, 0.0, sliceFrontPlane, originalSize[2]);
pickPlaneActorPointSet->GetPoints()->SetPoint(3, 0.0, sliceBackPlane, originalSize[2]);
pickPlaneActorPointSet->GetPoints()->SetPoint(4, originalSize[0], sliceBackPlane, 0.0);
pickPlaneActorPointSet->GetPoints()->SetPoint(5, originalSize[0], sliceFrontPlane, 0.0);
pickPlaneActorPointSet->GetPoints()->SetPoint(6, originalSize[0], sliceFrontPlane, originalSize[2]);
pickPlaneActorPointSet->GetPoints()->SetPoint(7, originalSize[0], sliceBackPlane, originalSize[2]);
break;
case SAGITTAL:
planeXCoord = currentSliceIndex + originalSpacing[0];
pickPlane->SetOrigin(planeXCoord, 0.0, 0.0);
pickPlane->SetPoint1(planeXCoord, originalSize[1], 0.0);
pickPlane->SetPoint2(planeXCoord, 0.0, originalSize[2]);
sliceBackPlane = currentSliceIndex*originalSpacing[0] - originalSpacing[0] / 2.0;
sliceFrontPlane = currentSliceIndex*originalSpacing[0] + originalSpacing[0] / 2.0;
pickPlaneActorPointSet->GetPoints()->SetPoint(0, sliceBackPlane, 0.0, 0.0);
pickPlaneActorPointSet->GetPoints()->SetPoint(1, sliceFrontPlane, 0.0, 0.0);
pickPlaneActorPointSet->GetPoints()->SetPoint(2, sliceFrontPlane, 0.0, originalSize[2]);
pickPlaneActorPointSet->GetPoints()->SetPoint(3, sliceBackPlane, 0.0, originalSize[2]);
pickPlaneActorPointSet->GetPoints()->SetPoint(4, sliceBackPlane, originalSize[1], 0.0);
pickPlaneActorPointSet->GetPoints()->SetPoint(5, sliceFrontPlane, originalSize[1], 0.0);
pickPlaneActorPointSet->GetPoints()->SetPoint(6, sliceFrontPlane, originalSize[1], originalSize[2]);
pickPlaneActorPointSet->GetPoints()->SetPoint(7, sliceBackPlane, originalSize[1], originalSize[2]);
break;
default:
break;
}
pickPlane->UpdateWholeExtent();
// Needed to notify the vtk pipeline of the change in the geometry (and therefore update the actor)
pickPlaneActorPointSet->Modified();
}
// ----------------- updatePixelActorPosition -----------------
......
......@@ -253,18 +253,11 @@ protected:
/** Smart pointer to the original volume to reslice (input of the vtk pipeline) */
vtkSmartPointer<vtkImageData> originalVolume;
/// Table containing first and last indices of the image in each direction
/// 0 & 1 -> x; 2 and 3 -> y; 4 & 5 -> z
int extent[6];
/** Keep track of the slice number */
int currentSliceIndex;
/// Common lookup table
vtkSmartPointer<vtkWindowLevelLookupTable> lut;
/** Original volume dimensions in number of voxels (x, y and z) */
int originalDimensions[3];
vtkSmartPointer<vtkWindowLevelLookupTable> lut;
/** Voxel size of the original image volume. Used to compute point coordinates between real world and index world.*/
double originalSpacing[3];
......@@ -272,9 +265,6 @@ protected:
/** Real size (originalDimension * originalSpacing in x, y and z) of the original image */
double originalSize[3];
/// To be able to extract a slice
vtkSmartPointer<vtkImageMapToColors> imgToMapFilter;
/// 3D actor
vtkSmartPointer<vtkImageActor> image3DActor;
......@@ -308,19 +298,13 @@ protected:
/// update the pixel actor to the middle of the current slice
void updatePixelActor();
/** A plane used for picking.
* This plane has the same size and position as the displayed slice. However, it is a
* real vtkActor that can be easily picked (using 'p' ot 'Ctrl+left click').
*/
vtkSmartPointer<vtkPlaneSource> pickPlane;
/** Mapper of the the pickPlane. */
vtkSmartPointer<vtkPolyDataMapper> pickPlaneMapper;
/** Actor representing the pickPlane. */
vtkSmartPointer<vtkActor> pickPlaneActor;
/// the pick plane actor unstructured grid
vtkSmartPointer<vtkUnstructuredGrid> pickPlaneActorPointSet;
/** Actor representing a pixel, displayed over the image. */
vtkSmartPointer<vtkActor> pixelActor;
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment