Currently job artifacts in CI/CD pipelines on LRZ GitLab never expire. Starting from Wed 26.1.2022 the default expiration time will be 30 days (GitLab default). Currently existing 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 1d6510ae authored by Christian Schulte zu Berge's avatar Christian Schulte zu Berge
Browse files

Introducing tensor module with sample data and early TensorAnalyzer processor

parent 94289f81
// ================================================================================================
//
// This file is part of the CAMPVis Software Framework.
//
// If not explicitly stated otherwise: Copyright (C) 2012-2013, all rights reserved,
// Christian Schulte zu Berge <christian.szb@in.tum.de>
// Chair for Computer Aided Medical Procedures
// Technische Universität München
// Boltzmannstr. 3, 85748 Garching b. München, 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 "tensoranalyzer.h"
#include "tgt/tgt_math.h"
#include "tgt/logmanager.h"
#include <Eigen/Eigenvalues>
#include <tbb/tbb.h>
#include <tbb/atomic.h>
#include "core/datastructures/imagedata.h"
namespace campvis {
static const GenericOption<TensorAnalyzer::DegeneratedEvHandling> handlingModes[4] = {
GenericOption<TensorAnalyzer::DegeneratedEvHandling>("none", "None", TensorAnalyzer::NONE),
GenericOption<TensorAnalyzer::DegeneratedEvHandling>("mask", "Mask", TensorAnalyzer::MASK),
GenericOption<TensorAnalyzer::DegeneratedEvHandling>("invert", "Invert", TensorAnalyzer::INVERT),
GenericOption<TensorAnalyzer::DegeneratedEvHandling>("shift", "Shift", TensorAnalyzer::SHIFT)
};
const std::string TensorAnalyzer::loggerCat_ = "CAMPVis.modules.classification.TensorAnalyzer";
TensorAnalyzer::TensorAnalyzer()
: AbstractProcessor()
, p_inputImage("InputImage", "Input Tensor Image", "tensors", DataNameProperty::READ, AbstractProcessor::INVALID_RESULT | EIGENSYSTEM_INVALID)
, p_evalsImage("EvalsImage", "Output Eigenvalues Image", "eigenvalues", DataNameProperty::WRITE)
, p_evecsImage("EvecsImage", "Output Eigenvectors Image", "eigenvectors", DataNameProperty::WRITE)
, p_degeneratedHandling("DegeneratedHandling", "Handling of Degenerated Tensors", handlingModes, 4)
, p_maskMixedTensors("MaskMixedTensors", "Mask Mixed Tensors", true)
, _eigenvalues(0)
, _eigenvectors(0)
{
addProperty(&p_inputImage);
addProperty(&p_evalsImage);
addProperty(&p_evecsImage);
addProperty(&p_degeneratedHandling);
addProperty(&p_maskMixedTensors);
}
TensorAnalyzer::~TensorAnalyzer() {
}
void TensorAnalyzer::process(DataContainer& data) {
if (getInvalidationLevel() & EIGENSYSTEM_INVALID) {
computeEigensystem(data);
}
if (_eigenvalues.getData() != 0 && _eigenvectors.getData() != 0) {
}
else {
LERROR("Could not compute Eigensystem");
}
validate(INVALID_RESULT);
}
void TensorAnalyzer::computeEigensystem(DataContainer& data) {
// get input tensor image
GenericImageRepresentationLocal<float, 6>::ScopedRepresentation input(data, p_inputImage.getValue());
if (input != 0) {
// create output images for eigenvalues (stored as vec3) and eigenvectors (stored as mat3)
ImageData* evals = new ImageData(input->getDimensionality(), input->getSize(), 3);
GenericImageRepresentationLocal<float, 3>* evalRep = GenericImageRepresentationLocal<float, 3>::create(evals, 0);
ImageData* evecs = new ImageData(input->getDimensionality(), input->getSize(), 9);
GenericImageRepresentationLocal<float, 9>* evecRep = GenericImageRepresentationLocal<float, 9>::create(evals, 0);
tbb::atomic<size_t> countDiscarded;
countDiscarded = 0;
DegeneratedEvHandling evh = p_degeneratedHandling.getOptionValue();
// perform eigen decomposition in parallel
tbb::parallel_for(tbb::blocked_range<size_t>(0, input->getNumElements()), [&] (const tbb::blocked_range<size_t>& range) {
for (size_t i = range.begin(); i != range.end(); ++i) {
const Tensor2<float>& t = input->getElement(i);
if (t.Dxx == 0 && t.Dxy == 0 && t.Dxz == 0 && t.Dyy == 0 && t.Dyz == 0 && t.Dzz == 0) {
evalRep->setElement(i, tgt::vec3::zero);
evecRep->setElement(i, tgt::mat3::zero);
continue;
}
// use Eigen library for eigensystem calculation
Eigen::Matrix3f tensorMatrix;
tensorMatrix << t.Dxx, t.Dxy, t.Dxz, t.Dxy, t.Dyy, t.Dyz, t.Dxz, t.Dyz, t.Dzz;
Eigen::SelfAdjointEigenSolver<Eigen::Matrix3f> solver(tensorMatrix);
// gather data from eigen library
const Eigen::Matrix<float, 3, 1>& values = solver.eigenvalues();
const Eigen::Matrix3f& vectors = solver.eigenvectors();
// kill NaN values
if (tgt::isNaN(values(0)) || tgt::isNaN(values(1)) || tgt::isNaN(values(2))) {
evalRep->setElement(i, tgt::vec3::zero);
evecRep->setElement(i, tgt::mat3::zero);
continue;
}
// determine order of eigenvalues (i.e. sort'em)
int high, mid, low;
bool negative = false;
if (values(0) > values(1)) {
if (values(1) > values(2)) {
high = 0; mid = 1; low = 2;
}
else if (values(0) > values(2)) {
high = 0; mid = 2; low = 1;
}
else {
high = 2; mid = 0; low = 1;
}
}
else {
if (values(0) > values(2)) {
high = 1; mid = 0; low = 2;
}
else if (values(1) > values(2)) {
high = 1; mid = 2; low = 0;
}
else {
high = 2; mid = 1; low = 0;
}
}
// perform handling of degenerated tensors (i.e. negative/mixed eigenvalues):
if (values(0) < 0 && values(1) < 0 && values(2) < 0) {
negative = true;
if (evh == INVERT)
std::swap(high, low);
}
else if (p_maskMixedTensors.getValue() && (values(0) < 0 || values(1) < 0 || values(2) < 0)) {
// We assume that either all eigenvalues are positive or all eigenvalues are negative.
// If we encounter both positive and negative eigenvalues this must be due to severe noise
// (e.g. area outside brain) so it is reasonable to discard these voxels.
++countDiscarded;
evalRep->setElement(i, tgt::vec3::zero);
evecRep->setElement(i, tgt::mat3::zero);
continue;
}
if (negative && evh == MASK) {
evalRep->setElement(i, tgt::vec3::zero);
evecRep->setElement(i, tgt::mat3::zero);
}
else {
evalRep->setElement(i, tgt::vec3(values(high), values(mid), values(low)));
evecRep->setElement(i, tgt::mat3(vectors(0, high), vectors(1, high), vectors(2, high),
vectors(0, mid), vectors(1, mid), vectors(2, mid),
vectors(0, low), vectors(1, low), vectors(2, low)));
if (negative && evh == SHIFT) {
evalRep->getElement(i) -= (evalRep->getElement(i).x + evalRep->getElement(i).z);
}
}
}
});
// write results to DataContainer and also cache them in local members
_eigenvalues = data.addData(p_evalsImage.getValue(), evals);
_eigenvectors = data.addData(p_evecsImage.getValue(), evecs);
}
else {
LDEBUG("No suitable input image found.");
}
validate(EIGENSYSTEM_INVALID);
}
}
// ================================================================================================
//
// This file is part of the CAMPVis Software Framework.
//
// If not explicitly stated otherwise: Copyright (C) 2012-2013, all rights reserved,
// Christian Schulte zu Berge <christian.szb@in.tum.de>
// Chair for Computer Aided Medical Procedures
// Technische Universität München
// Boltzmannstr. 3, 85748 Garching b. München, 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 TENSORANALYZER_H__
#define TENSORANALYZER_H__
#include <string>
#include "core/pipeline/visualizationprocessor.h"
#include "core/properties/datanameproperty.h"
#include "core/properties/genericproperty.h"
#include "core/properties/floatingpointproperty.h"
#include "core/properties/numericproperty.h"
#include "core/properties/optionproperty.h"
#include "core/datastructures/genericimagerepresentationlocal.h"
namespace campvis {
/**
* Performs eigensystem decomposition of a tensor image and also computes different anisotropy measures.
*/
class TensorAnalyzer : public AbstractProcessor {
public:
enum AdditionalInvalidationLevels {
EIGENSYSTEM_INVALID = AbstractProcessor::FIRST_FREE_TO_USE_INVALIDATION_LEVEL
};
/// Eigenvalue handling of degenerated tensors (i.e. having (partially) negative eigenvalues)
enum DegeneratedEvHandling {
NONE, MASK, INVERT, SHIFT
};
/**
* Constructs a new TensorAnalyzer Processor
**/
TensorAnalyzer();
/**
* Destructor
**/
virtual ~TensorAnalyzer();
/// \see AbstractProcessor::getName()
virtual const std::string getName() const { return "TensorAnalyzer"; };
/// \see AbstractProcessor::getDescription()
virtual const std::string getDescription() const { return "Performs eigensystem decomposition of a tensor image and also computes different anisotropy measures."; };
/// \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::EXPERIMENTAL; };
virtual void process(DataContainer& data);
DataNameProperty p_inputImage; ///< ID for input volume
DataNameProperty p_evalsImage; ///< ID for output gradient volume
DataNameProperty p_evecsImage; ///< ID for output gradient volume
GenericOptionProperty<DegeneratedEvHandling> p_degeneratedHandling; ///< Handling of degenerated tensors
BoolProperty p_maskMixedTensors;
protected:
/**
* Computes the eigensystem for the given tensor image \a tensorImage.
* Computed eigenvalues and eigenvectors are stored in this' class local members.
* \param data DataContainer to work on.
*/
void computeEigensystem(DataContainer& data);
DataHandle _eigenvalues; ///< Current eigenvalues cached
DataHandle _eigenvectors; ///< Current eigenvectors cached
static const std::string loggerCat_;
};
}
#endif // TENSORANALYZER_H__
NDims = 3
DimSize = 56 55 12
ElementType = MET_FLOAT
ElementSpacing = 1.000000 1.000000 1.000000
ElementByteOrderMSB = False
ElementDataFile = planar_img.raw
ObjectType = TensorImage
NDims = 3
DimSize = 56 55 12
ElementType = MET_FLOAT
ElementSpacing = 1.000000 1.000000 1.000000
ElementByteOrderMSB = False
ElementDataFile = planar_tensor.raw
# CMake file for Tensor module
# Source files:
FILE(GLOB ThisModSources RELATIVE ${ModulesDir}
modules/tensor/processors/*.cpp
modules/tensor/pipelines/*.cpp
)
# Header files
FILE(GLOB ThisModHeaders RELATIVE ${ModulesDir}
modules/tensor/glsl/*.frag
modules/tensor/processors/*.h
modules/tensor/pipelines/*.h
)
SET(ThisModShaderDirectories "modules/pipelines/glsl")
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