diff --git a/sdk/libraries/core/component/Slice.cpp b/sdk/libraries/core/component/Slice.cpp index db8787e31148c439bdbdbddaea19bd16d38874e5..e618766412f7393d017335946fec94b1e51ca6ea 100644 --- a/sdk/libraries/core/component/Slice.cpp +++ b/sdk/libraries/core/component/Slice.cpp @@ -36,6 +36,7 @@ #include #include #include +#include using namespace std; @@ -61,7 +62,7 @@ Slice::~Slice() { void Slice::init() { currentSliceIndex = 0; - for (int i=0; i<3; i++) { + for (int i = 0; i < 3; i++) { originalSpacing[i] = 1.0; } @@ -72,13 +73,8 @@ void Slice::init() { pickPlaneActorPointSet = nullptr; pixelActor = nullptr; pixelActorPointSet = nullptr; - - image2DChangeInfo = vtkSmartPointer::New(); - image2DReslicer = vtkSmartPointer::New(); - transformReslicer = vtkSmartPointer::New(); - resliceTransform = vtkSmartPointer::New(); - transformReslicer->Identity(); - resliceTransform->Identity(); + image2DReslicer = nullptr; + resliceTransform = nullptr; } // -------------------- getImageData -------------------- @@ -93,22 +89,22 @@ void Slice::setOriginalVolume(vtkSmartPointer volume) { originalVolume = volume; // Get the original volume information - // Original volume dimensions in number of voxels (x, y and z) - int originalDimensions[3]; + // Original volume dimensions in number of voxels (x, y and z) + int originalDimensions[3]; originalVolume->GetDimensions(originalDimensions); originalVolume->GetSpacing(originalSpacing); - for (int i=0; i<3; i++) { + 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(); } @@ -143,12 +139,12 @@ int Slice::getNumberOfSlices() const { // 0 & 1 -> x; 2 and 3 -> y; 4 & 5 -> z int extent[6]; originalVolume->GetExtent(extent); - + int nbSlices; switch (sliceOrientation) { - case ARBITRARY: - nbSlices = max(max(extent[1] - extent[0], extent[3] - extent[2]), extent[5] - extent[4]) + 1; - break; + case ARBITRARY: + nbSlices = max(max(extent[1] - extent[0], extent[3] - extent[2]), extent[5] - extent[4]) + 1; + break; case AXIAL: case AXIAL_NEURO: nbSlices = extent[5] - extent[4] + 1; @@ -193,9 +189,9 @@ void Slice::setSlice(int s) { originalVolume->GetExtent(extent); switch (sliceOrientation) { - case ARBITRARY: - updateReslice(); - break; + case ARBITRARY: + updateReslice(); + break; case AXIAL: case AXIAL_NEURO: image3DActor->SetDisplayExtent(extent[0], extent[1], extent[2], extent[3], s, s); @@ -245,9 +241,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(); + pixelActor->VisibilityOn(); } // -------------------- getNumberOfColors -------------------- @@ -263,45 +259,11 @@ void Slice::setPixelRealPosition(double x, double y, double z) { // -------------------- setReslicerTransform -------------------- void Slice::setReslicerTransform(vtkSmartPointer t) { // store the transformation pointer - // (when the transformation is changed, you need to call updateReslice() in order + // (when the transformation is changed, you need to call updateReslice() in order // to take the new transformation into account and update the arbitrary slice image - resliceTransform = t; + resliceTransform = t; } -// -------------------- updateReslice -------------------- -void Slice::updateReslice() { - if (sliceOrientation == ARBITRARY) { - double wxyz[4]; - - transformReslicer->Identity(); - transformReslicer->Translate(originalDimensions[0] / 2, originalDimensions[1] / 2, originalDimensions[2] / 2); - resliceTransform->GetOrientationWXYZ(wxyz); - transformReslicer->RotateWXYZ(wxyz[0], wxyz[1], wxyz[2], wxyz[3]); - - transformReslicer->Translate(-originalDimensions[0] / 2, -originalDimensions[1] / 2, -originalDimensions[2] / 2); - transformReslicer->Translate(0.0, 0.0, currentSliceIndex); - - image2DReslicer->SetResliceAxes(transformReslicer->GetMatrix()); - image2DReslicer->Update(); - - image3DActor->SetUserMatrix(resliceTransform->GetMatrix()); - image3DActor->SetInputData(image2DReslicer->GetOutput()); - image3DActor->Update(); - } - - updateLocalTransformation(); - updatePickPlane(); -} - -// -------------------- updateLocalTransformation -------------------- -void Slice::updateLocalTransformation() { - image2DReslicer->SetOutputDimensionality(3); - image2DReslicer->UpdateInformation(); - - image2DReslicer->SetOutputDimensionality(2); - image2DReslicer->UpdateInformation(); - -} // -------------------- get2DImageActor -------------------- vtkSmartPointer Slice::get2DImageActor() const { return image2DActor; @@ -331,15 +293,6 @@ void Slice::initActors() { // set the lookupTable imgToMapFilter->SetLookupTable(lut); - image2DChangeInfo->SetInputData(imgToMapFilter->GetOutput()); - image2DChangeInfo->SetOutputOrigin(0.0, 0.0, 0.0); - image2DReslicer->SetInputConnection(image2DChangeInfo->GetOutputPort()); - image2DReslicer->SetInformationInput(imgToMapFilter->GetOutput()); - image2DReslicer->AutoCropOutputOn(); - image2DReslicer->SetOutputOriginToDefault(); - image2DReslicer->SetOutputExtentToDefault(); - image2DReslicer->SetOutputSpacingToDefault(); - // the 2D and 3D image actors are directly plugged to the output of imgToMapFilter image3DActor = vtkSmartPointer::New(); image3DActor->GetMapper()->SetInputConnection(imgToMapFilter->GetOutputPort()); @@ -349,11 +302,30 @@ void Slice::initActors() { image2DActor->GetMapper()->SetInputConnection(imgToMapFilter->GetOutputPort()); image2DActor->InterpolateOn(); - // Pick plane + // reslicer for the arbitrary slice + // image2DChangeInfo builds a simple image from the original image + // that has default origin, extent and spacing + vtkSmartPointer image2DChangeInfo = vtkSmartPointer::New(); + image2DChangeInfo->SetInputData(imgToMapFilter->GetOutput()); + image2DChangeInfo->SetOutputOrigin(0.0, 0.0, 0.0); + + image2DReslicer = vtkSmartPointer::New(); + image2DReslicer->SetInputConnection(image2DChangeInfo->GetOutputPort()); + image2DReslicer->SetInformationInput(imgToMapFilter->GetOutput()); + image2DReslicer->AutoCropOutputOn(); + image2DReslicer->SetOutputOriginToDefault(); + image2DReslicer->SetOutputExtentToDefault(); + image2DReslicer->SetOutputSpacingToDefault(); + + // use identity by default for the arbitrary slice + resliceTransform = vtkSmartPointer::New(); + resliceTransform->Identity(); + + // Picked plane initPickPlaneActor(); updatePickPlane(); - // pixel + // Picked pixel initPixelActor(); updatePixelActor(); } @@ -381,7 +353,7 @@ void Slice::initPickPlaneActor() { topQuad->GetPointIds()->SetId(1, 5); topQuad->GetPointIds()->SetId(2, 1); topQuad->GetPointIds()->SetId(3, 0); - + vtkSmartPointer rightQuad = vtkSmartPointer::New(); rightQuad->GetPointIds()->SetId(0, 5); rightQuad->GetPointIds()->SetId(1, 4); @@ -393,7 +365,7 @@ void Slice::initPickPlaneActor() { 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::New(); pickPlaneActorPointSet->Allocate(4); @@ -439,7 +411,7 @@ void Slice::initPickPlaneActor() { //-- pickPlaneActor can not be picked pickPlaneActor->PickableOff(); - + // by default, the plane actor is always visible pickPlaneActor->VisibilityOn(); } @@ -527,8 +499,8 @@ void Slice::updatePickPlane() { switch (sliceOrientation) { case AXIAL_NEURO: case AXIAL: - sliceBackPlane = currentSliceIndex*originalSpacing[2] - originalSpacing[2] / 2.0; - sliceFrontPlane = currentSliceIndex*originalSpacing[2] + originalSpacing[2] / 2.0; + 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); @@ -540,8 +512,8 @@ void Slice::updatePickPlane() { break; case CORONAL: - sliceBackPlane = currentSliceIndex*originalSpacing[1] - originalSpacing[1] / 2.0; - sliceFrontPlane = currentSliceIndex*originalSpacing[1] + originalSpacing[1] / 2.0; + 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]); @@ -553,8 +525,8 @@ void Slice::updatePickPlane() { break; case SAGITTAL: - sliceBackPlane = currentSliceIndex*originalSpacing[0] - originalSpacing[0] / 2.0; - sliceFrontPlane = currentSliceIndex*originalSpacing[0] + originalSpacing[0] / 2.0; + 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]); @@ -636,6 +608,52 @@ void Slice::updatePixelActor(double x, double y, double z) { pixelActorPointSet->Modified(); } +// -------------------- updateReslice -------------------- +void Slice::updateReslice() { + if (sliceOrientation == ARBITRARY) { + double wxyz[4]; + + // Original volume dimensions in number of voxels (x, y and z) + int originalDimensions[3]; + originalVolume->GetDimensions(originalDimensions); + + // Transformations required to compute the arbitrary slice inside the volume + vtkSmartPointer transformationInsideVolume = vtkTransform::New(); + + transformationInsideVolume->Identity(); + // go to the image center + transformationInsideVolume->Translate(originalDimensions[0] / 2, originalDimensions[1] / 2, originalDimensions[2] / 2); + // apply the current rotation from the frame + resliceTransform->GetOrientationWXYZ(wxyz); + transformationInsideVolume->RotateWXYZ(wxyz[0], wxyz[1], wxyz[2], wxyz[3]); + // go back to the image origin + transformationInsideVolume->Translate(-originalDimensions[0] / 2, -originalDimensions[1] / 2, -originalDimensions[2] / 2); + // translate to the current slice + transformationInsideVolume->Translate(0.0, 0.0, currentSliceIndex); + + image2DReslicer->SetResliceAxes(transformationInsideVolume->GetMatrix()); + image2DReslicer->Update(); + + image3DActor->SetUserMatrix(resliceTransform->GetMatrix()); + image3DActor->SetInputData(image2DReslicer->GetOutput()); + image3DActor->Update(); + } + + updateLocalTransformation(); + updatePickPlane(); +} + +// -------------------- updateLocalTransformation -------------------- +void Slice::updateLocalTransformation() { + image2DReslicer->SetOutputDimensionality(3); + image2DReslicer->UpdateInformation(); + + image2DReslicer->SetOutputDimensionality(2); + image2DReslicer->UpdateInformation(); + +} + + diff --git a/sdk/libraries/core/component/Slice.h b/sdk/libraries/core/component/Slice.h index e2540e98a0ae6f5949745cfb9a3098f744ba93ed..3967bc0d47e7cb6e9d7762d8127fc959721915bc 100644 --- a/sdk/libraries/core/component/Slice.h +++ b/sdk/libraries/core/component/Slice.h @@ -37,6 +37,7 @@ #include #include #include +#include namespace camitk { /** @@ -47,6 +48,8 @@ namespace camitk { * * This class manages the visual representation of one slice of a volume image. * The slice depends on the orientation and the currently selected slice index. + * A special case is the arbitrary orientation, where the orientation is not perpendicular to one + * of the axes, but can have any free orientation and translation. * * A slice is represented in 2D and 3D thanks to: * - image2DActor that provides a 2D representation (the slice in the world reference frame) @@ -302,26 +305,18 @@ protected: /// @name Management of the arbitrary slice /// @{ + + /// Transformation relative to the 3D image actor (it is not parallel to one + /// of the main axe, but has a specific rotation). This should be initialized + /// using the single image component transformation to the world or parent. + vtkSmartPointer resliceTransform; - /// Transformations required to compute the arbitrary slice inside the volume - /// this can be any transform, unlike the image orientation - vtkSmartPointer transformReslicer; - - /// Transformation relative to the 3D image actor (it is not parallel to one - /// of the main axe, but has a specific rotation) - vtkSmartPointer resliceTransform; + /// The image reslicer computes the arbitrary slice pixels + vtkSmartPointer image2DReslicer; /// update the 2D reslicer void updateLocalTransformation(); - /// This is required for the arbitrary slice. - /// It builds a simple image from the original image - /// that has default origin, extent and spacing - vtkSmartPointer image2DChangeInfo; - - /// The image reslicer computes the arbitrary slice pixels - vtkSmartPointer image2DReslicer; - /// @} /// @name Used to visualize the current picking @@ -376,3 +371,4 @@ protected: } #endif // CANONICAL_SLICE_H +