The expiration time for new job artifacts in CI/CD pipelines is now 30 days (GitLab default). Previously generated artifacts in already completed jobs will not be affected by the change. The latest artifacts for all jobs in the latest successful pipelines will be kept. More information: https://gitlab.lrz.de/help/user/admin_area/settings/continuous_integration.html#default-artifacts-expiration

Commit 24d8c9e1 authored by Christian Schulte zu Berge's avatar Christian Schulte zu Berge
Browse files

Fixed registration module:

* fixed ITK module CMake file so that it no longer breaks NLOpt
* Introducing RegistrationSliceView processor rendering the slice of the moving image with respect to the fixed image for registration purposes.
* Overhauled NloptRegistration pipeline to the updated CAMPVis API. Now again, working like a charm.
parent bb1f62f5
......@@ -4,8 +4,6 @@ IF(${ModuleEnabled})
FIND_PACKAGE (ITK REQUIRED)
IF(ITK_FOUND)
INCLUDE( ${ITK_USE_FILE} )
# Since we use ITK classes also in the core module, we need to set global include dirs and global external libs
LIST(APPEND CampvisGlobalIncludeDirs ${ITK_INCLUDE_DIRS})
LIST(APPEND CampvisGlobalExternalLibs ${ITK_LIBRARIES})
......
......@@ -139,10 +139,7 @@ SET ( NLOPT_SOURCES
stogo/global.cc stogo/linalg.cc stogo/local.cc stogo/stogo.cc stogo/tools.cc stogo/global.h stogo/linalg.h stogo/local.h stogo/stogo_config.h stogo/stogo.h stogo/tools.h
)
OPTION(NLOPT_BUILD_SHARED "Build NLOPT as a shared library" OFF )
#FIXME: CAMPVIs currently does not support shared linking of modules -> nlopt needs to be linked statically
SET(NLOPT_BUILD_SHARED FALSE)
OPTION(NLOPT_BUILD_SHARED "Build NLOPT as a shared library" ON)
IF(NLOPT_BUILD_SHARED)
ADD_DEFINITIONS(-DNLOPT_DLL)
......
......@@ -25,16 +25,34 @@
#include "nloptregistration.h"
#include "cgt/event/keyevent.h"
#include "cgt/openglgarbagecollector.h"
#include "cgt/opengljobprocessor.h"
#include "cgt/painter.h"
#include "core/classification/geometry1dtransferfunction.h"
#include "core/classification/tfgeometry1d.h"
#include "core/datastructures/renderdata.h"
#include "core/datastructures/transformdata.h"
#include "core/tools/glreduction.h"
namespace campvis {
namespace registration {
namespace {
cgt::mat4 euleranglesToMat4(const cgt::vec3& eulerAngles) {
float sinX = sin(eulerAngles.x);
float cosX = cos(eulerAngles.x);
float sinY = sin(eulerAngles.y);
float cosY = cos(eulerAngles.y);
float sinZ = sin(eulerAngles.z);
float cosZ = cos(eulerAngles.z);
cgt::mat4 toReturn(cosY * cosZ, cosZ * sinX * sinY - cosX * sinZ, sinX * sinZ + cosX * cosZ * sinY, 0.f,
cosY * sinZ, sinX * sinY * sinZ + cosX * cosZ, cosX * sinY * sinZ - cosZ * sinX, 0.f,
(-1) * sinY, cosY * sinX, cosX * cosY, 0.f,
0.f, 0.f, 0.f, 1.f);
return toReturn;
}
}
static const GenericOption<nlopt::algorithm> optimizers[3] = {
GenericOption<nlopt::algorithm>("cobyla", "COBYLA", nlopt::LN_COBYLA),
GenericOption<nlopt::algorithm>("newuoa", "NEWUOA", nlopt::LN_NEWUOA),
......@@ -52,6 +70,7 @@ namespace campvis {
, _lsp()
, _referenceReader()
, _movingReader()
, _rsw(&_canvasSize)
, _sm()
, _ve(&_canvasSize)
, _opt(0)
......@@ -59,6 +78,7 @@ namespace campvis {
addProcessor(&_lsp);
addProcessor(&_referenceReader);
addProcessor(&_movingReader);
addProcessor(&_rsw);
addProcessor(&_sm);
addProcessor(&_ve);
......@@ -84,23 +104,28 @@ namespace campvis {
_referenceReader.p_url.setValue("D:/Medical Data/SCR/Data/RegSweeps_phantom_cropped/-1S1median/Volume_2.mhd");
_referenceReader.p_targetImageID.setValue("Reference Image");
_referenceReader.p_targetImageID.addSharedProperty(&_sm.p_referenceId);
_referenceReader.p_targetImageID.addSharedProperty(&_rsw.p_sourceImageID);
_movingReader.p_url.setValue("D:/Medical Data/SCR/Data/RegSweeps_phantom_cropped/-1S1median/Volume_3.mhd");
_movingReader.p_targetImageID.setValue("Moving Image");
_movingReader.p_targetImageID.addSharedProperty(&_sm.p_movingId);
_movingReader.p_targetImageID.addSharedProperty(&_rsw.p_movingImage);
_rsw.p_movingTransformationMatrix.setValue("trafoMatrix");
_rsw.p_targetImageID.setValue("RegistrationSliceView");
_sm.p_differenceImageId.addSharedProperty(&_ve.p_inputVolume);
_sm.p_metric.selectById("NCC");
_ve.p_outputImage.setValue("volumeexplorer");
_renderTargetID.setValue("volumeexplorer");
_renderTargetID.setValue("RegistrationSliceView");
Geometry1DTransferFunction* dvrTF = new Geometry1DTransferFunction(128, cgt::vec2(-1.f, 1.f));
dvrTF->addGeometry(TFGeometry1D::createQuad(cgt::vec2(0.f, .5f), cgt::col4(0, 0, 255, 255), cgt::col4(255, 255, 255, 0)));
dvrTF->addGeometry(TFGeometry1D::createQuad(cgt::vec2(.5f, 1.f), cgt::col4(255, 255, 255, 0), cgt::col4(255, 0, 0, 255)));
MetaProperty* mp = static_cast<MetaProperty*>(_ve.getProperty("SliceExtractorProperties"));
static_cast<TransferFunctionProperty*>(mp->getProperty("transferFunction"))->replaceTF(dvrTF);
static_cast<TransferFunctionProperty*>(mp->getProperty("transferFunction"))->setAutoFitWindowToData(false);
MetaProperty* mp = dynamic_cast<MetaProperty*>(_ve.getProperty("SliceExtractorProperties"));
dynamic_cast<TransferFunctionProperty*>(mp->getProperty("TransferFunction"))->replaceTF(dvrTF);
dynamic_cast<TransferFunctionProperty*>(mp->getProperty("TransferFunction"))->setAutoFitWindowToData(false);
}
void NloptRegistration::deinit() {
......@@ -110,14 +135,17 @@ namespace campvis {
AutoEvaluationPipeline::deinit();
}
void NloptRegistration::onProcessorValidated(AbstractProcessor* processor) {
}
void NloptRegistration::onPerformOptimizationClicked() {
// Evaluation of the similarity measure needs an OpenGL context, so we need to create an OpenGL job for this.
cgt::GLContextScopedLock lockGuard(_canvas);
performOptimization();
// we want the registration to be performed in a background thread and not in the signal_manager's thread.
std::thread registrationThread([this] () {
// Evaluation of the similarity measure needs an OpenGL context, so we need to acquire the canvas' context.
// An alternative solution would be to specialize the pipeline and overload the executePipeline() method.
// Then the registration would be performed in the pipeline's thread, which is probably the mor beautiful
// solution.
cgt::GLContextScopedLock lockGuard(this->_canvas);
this->performOptimization();
});
}
void NloptRegistration::performOptimization() {
......@@ -195,19 +223,16 @@ namespace campvis {
// perform interactive update if wished
if (mfd->_object->p_liveUpdate.getValue()) {
// compute difference image
mfd->_object->_sm.generateDifferenceImage(mfd->_object->_data, mfd->_reference, mfd->_moving, translation, rotation);
cgt::mat4 trafoMatrix = cgt::mat4::createTranslation(translation) * euleranglesToMat4(rotation);
mfd->_object->getDataContainer().addData("trafoMatrix", new TransformData(trafoMatrix));
// render difference volume
mfd->_object->_ve.process(mfd->_object->getDataContainer());
// render slice view
mfd->_object->_rsw.process(mfd->_object->getDataContainer());
// update canvas
mfd->_object->_canvas->getPainter()->paint();
}
// clean up unused GL textures.
GLGC.deleteGarbage();
return similarity;
}
......@@ -215,6 +240,10 @@ namespace campvis {
if (_opt != 0)
_opt->force_stop();
}
}
// Instantiate template to register the pipelines.
template class PipelineRegistrar<registration::NloptRegistration>;
}
......@@ -31,16 +31,20 @@
#include "core/pipeline/autoevaluationpipeline.h"
#include "modules/modulesapi.h"
#include "modules/pipelinefactory.h"
#include "modules/base/processors/lightsourceprovider.h"
#include "modules/io/processors/mhdimagereader.h"
#include "modules/vis/processors/volumeexplorer.h"
#include "modules/preprocessing/processors/gradientvolumegenerator.h"
#include "modules/preprocessing/processors/lhhistogram.h"
#include "modules/registration/processors/registrationsliceview.h"
#include "modules/registration/processors/similaritymeasure.h"
#include <nlopt.hpp>
namespace campvis {
namespace registration {
class CAMPVIS_MODULES_API NloptRegistration : public AutoEvaluationPipeline {
public:
/**
......@@ -60,7 +64,7 @@ namespace campvis {
/// \see AbstractPipeline::getName()
virtual const std::string getName() const { return getId(); };
static const std::string getId() { return "NloptRegistration"; };
static const std::string getId() { return "registration::NloptRegistration"; };
GenericOptionProperty<nlopt::algorithm> p_optimizer; ///< Optimizer Algorithm
BoolProperty p_liveUpdate; ///< Live Update of the difference image
......@@ -79,13 +83,6 @@ namespace campvis {
size_t _count;
};
/**
* Slot getting called when one of the observed processors got validated.
* Updates the camera properties, when the input image has changed.
* \param processor The processor that emitted the signal
*/
virtual void onProcessorValidated(AbstractProcessor* processor);
/**
* Callback method called from p_performOptimization.
* (Does not need an OpenGL context)
......@@ -117,6 +114,7 @@ namespace campvis {
LightSourceProvider _lsp;
MhdImageReader _referenceReader;
MhdImageReader _movingReader;
RegistrationSliceView _rsw;
SimilarityMeasure _sm;
VolumeExplorer _ve;
......@@ -125,4 +123,6 @@ namespace campvis {
}
}
#endif // NLOPTREGISTRATION_H__
// ================================================================================================
//
// This file is part of the CAMPVis Software Framework.
//
// If not explicitly stated otherwise: Copyright (C) 2012-2014, all rights reserved,
// Christian Schulte zu Berge <christian.szb@in.tum.de>
// Chair for Computer Aided Medical Procedures
// Technische Universitaet Muenchen
// Boltzmannstr. 3, 85748 Garching b. Muenchen, Germany
//
// For a full list of authors and contributors, please refer to the file "AUTHORS.txt".
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
// except in compliance with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software distributed under the
// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
// either express or implied. See the License for the specific language governing permissions
// and limitations under the License.
//
// ================================================================================================
#include "registrationsliceview.h"
#include "cgt/glmath.h"
#include "cgt/logmanager.h"
#include "cgt/shadermanager.h"
#include "cgt/textureunit.h"
#include "core/classification/simpletransferfunction.h"
#include "core/datastructures/imagedata.h"
#include "core/datastructures/imagerepresentationgl.h"
#include "core/datastructures/renderdata.h"
#include "core/datastructures/transformdata.h"
#include "core/tools/quadrenderer.h"
namespace campvis {
namespace registration {
const std::string RegistrationSliceView::loggerCat_ = "CAMPVis.modules.vis.RegistrationSliceView";
RegistrationSliceView::RegistrationSliceView(IVec2Property* viewportSizeProp)
: SliceRenderProcessor(viewportSizeProp, "modules/registration/glsl/registrationsliceview.frag", "")
, p_movingImage("MovingImage", "Moving Image", "movingImage", DataNameProperty::READ)
, p_movingTransformationMatrix("MovingTransformationMatrix", "Moving Image Transformation Matrix", "movingImage.transformation", DataNameProperty::READ)
, p_referenceTransferFunction("ReferenceTransferFunction", "Reference Image Transfer Function", new SimpleTransferFunction(256))
, p_movingTransferFunction("MovingTransferFunction", "Moving Image Transfer Function", new SimpleTransferFunction(256))
{
addProperty(p_movingImage, INVALID_PROPERTIES | INVALID_RESULT);
addProperty(p_movingTransformationMatrix);
addProperty(p_referenceTransferFunction);
addProperty(p_movingTransferFunction);
dynamic_cast<SimpleTransferFunction*>(p_movingTransferFunction.getTF())->setRightColor(cgt::col4(192, 128, 32, 255));
}
RegistrationSliceView::~RegistrationSliceView() {
}
void RegistrationSliceView::renderImageImpl(DataContainer& dataContainer, const ImageRepresentationGL::ScopedRepresentation& refImage) {
ImageRepresentationGL::ScopedRepresentation movImage(dataContainer, p_movingImage.getValue());
ScopedTypedData<TransformData> movingTrafo(dataContainer, p_movingTransformationMatrix.getValue());
if (movImage) {
// prepare OpenGL
_shader->activate();
cgt::TextureUnit refImageUnit, refTfUnit, movImageUnit, movTfUnit;
refImage->bind(_shader, refImageUnit, "_refImage", "_refImageParams");
movImage->bind(_shader, movImageUnit, "_movImage", "_movImageParams");
p_referenceTransferFunction.getTF()->bind(_shader, refTfUnit, "_refTf", "_refTfParams");
p_movingTransferFunction.getTF()->bind(_shader, movTfUnit, "_movTf", "_movTfParams");
cgt::mat4 identity = cgt::mat4::identity;
cgt::mat4 trafoMatrix = (movingTrafo ? movingTrafo->getTransform() : cgt::mat4::identity);
cgt::mat4 trafoMatrixInverse;
if (! trafoMatrix.invert(trafoMatrixInverse)) {
LERROR("Could not invert registration transformation matrix, using identity transformation!");
trafoMatrixInverse = cgt::mat4::identity;
}
cgt::Bounds movingBounds = movImage->getParent()->getWorldBounds();
cgt::vec3 halfDiagonal = movingBounds.getLLF() + (movingBounds.diagonal() / 2.f);
_shader->setUniform("_texCoordsMatrix", _texCoordMatrix);
_shader->setUniform("_modelMatrix", identity);
_shader->setUniform("_viewMatrix", _viewMatrix);
_shader->setUniform("_projectionMatrix", identity);
_shader->setUniform("_trafoMatrix", trafoMatrixInverse);
_shader->setUniform("_halfDiagonal", halfDiagonal);
_shader->setUniform("_useTexturing", true);
// render slice
FramebufferActivationGuard fag(this);
createAndAttachColorTexture();
createAndAttachDepthTexture();
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
QuadRdr.renderQuad();
_shader->deactivate();
cgt::TextureUnit::setZeroUnit();
dataContainer.addData(p_targetImageID.getValue(), new RenderData(_fbo));
}
}
void RegistrationSliceView::updateProperties(DataContainer& dataContainer) {
ScopedTypedData<ImageData> refImage(dataContainer, p_sourceImageID.getValue());
ScopedTypedData<ImageData> movingImage(dataContainer, p_movingImage.getValue());
p_referenceTransferFunction.setImageHandle(refImage.getDataHandle());
p_movingTransferFunction.setImageHandle(movingImage.getDataHandle());
SliceRenderProcessor::updateProperties(dataContainer);
}
}
}
// ================================================================================================
//
// This file is part of the CAMPVis Software Framework.
//
// If not explicitly stated otherwise: Copyright (C) 2012-2014, all rights reserved,
// Christian Schulte zu Berge <christian.szb@in.tum.de>
// Chair for Computer Aided Medical Procedures
// Technische Universitaet Muenchen
// Boltzmannstr. 3, 85748 Garching b. Muenchen, Germany
//
// For a full list of authors and contributors, please refer to the file "AUTHORS.txt".
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
// except in compliance with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software distributed under the
// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
// either express or implied. See the License for the specific language governing permissions
// and limitations under the License.
//
// ================================================================================================
#ifndef REGISTRATIONSLICEVIEW_H__
#define REGISTRATIONSLICEVIEW_H__
#include <string>
#include "core/pipeline/slicerenderprocessor.h"
#include "core/properties/allproperties.h"
#include "modules/modulesapi.h"
namespace campvis {
namespace registration {
/**
* Renders the slice of the moving image with respect to the fixed image for registration purposes.
*/
class CAMPVIS_MODULES_API RegistrationSliceView : public SliceRenderProcessor {
public:
/**
* Constructs a new RegistrationSliceView Processor
**/
RegistrationSliceView(IVec2Property* viewportSizeProp);
/**
* Destructor
**/
virtual ~RegistrationSliceView();
/// \see AbstractProcessor::getName()
virtual const std::string getName() const { return "RegistrationSliceView"; };
/// \see AbstractProcessor::getDescription()
virtual const std::string getDescription() const { return "Renders the slice of the moving image with respect to the fixed image for registration purposes."; };
/// \see AbstractProcessor::getAuthor()
virtual const std::string getAuthor() const { return "Christian Schulte zu Berge <christian.szb@in.tum.de>"; };
/// \see AbstractProcessor::getProcessorState()
virtual ProcessorState getProcessorState() const { return AbstractProcessor::TESTING; };
DataNameProperty p_movingImage; ///< ID for moving image
DataNameProperty p_movingTransformationMatrix; ///< ID for TransformData of moving image
TransferFunctionProperty p_referenceTransferFunction; ///< Transfer function for first image
TransferFunctionProperty p_movingTransferFunction; ///< Transfer function for second image
protected:
/// \see SliceRenderProcessor::renderImageImpl
virtual void renderImageImpl(DataContainer& dataContainer, const ImageRepresentationGL::ScopedRepresentation& img) override;
/// \see AbstractProcessor::updateProperties
virtual void updateProperties(DataContainer& dataContainer) override;
private:
static const std::string loggerCat_;
};
}
}
#endif // REGISTRATIONSLICEVIEW_H__
......@@ -39,6 +39,7 @@
#include "core/tools/quadrenderer.h"
namespace campvis {
namespace registration {
static const GenericOption<std::string> metrics[5] = {
GenericOption<std::string>("SUM", "Sum"),
......@@ -335,6 +336,5 @@ namespace campvis {
return w2t * cgt::mat4::createTranslation(halfDiagonal) * registrationInverse * cgt::mat4::createTranslation(-halfDiagonal) * t2w;
}
}
}
......@@ -53,6 +53,8 @@ namespace campvis {
class ImageRepresentationGL;
class GlReduction;
namespace registration {
/**
* Computes a Similarity Measure using OpenGL
*/
......@@ -163,5 +165,6 @@ namespace campvis {
};
}
}
#endif // SIMILARITYMEASURE_H__
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