Commit 17c2b876 authored by Jakob Weiss's avatar Jakob Weiss

Merge branch 'cppcourse-ws17' into 'cppcourse-ws17'

Cppcourse ws17

See merge request !8
parents 35d262db 73afddbe
......@@ -128,6 +128,8 @@ TARGET_LINK_LIBRARIES(campvis-application-lib ${CampvisMainLibs} ${CampvisGlobal
# the code to export DLL symbols
SET_TARGET_PROPERTIES(campvis-application-lib PROPERTIES DEFINE_SYMBOL "CAMPVIS_APPLICATION_BUILD_DLL")
target_compile_definitions(campvis-application-lib PUBLIC ${CampvisGlobalDefinitions} ${CampvisModulesDefinitions} ${CampvisApplicationDefinitions})
target_include_directories(campvis-application-lib PUBLIC ${CampvisGlobalIncludeDirs} ${CampvisModulesIncludeDirs})
IF(CAMPVIS_GROUP_SOURCE_FILES)
DEFINE_SOURCE_GROUPS_FROM_SUBDIR(CampvisApplicationSources ${CampvisHome} "")
......
# modules/cppcourse/cppcourse.cmake
# CMake file for the Module for the Cpp Course in WS 17
# Set module status (valid values are STABLE, TESTING and EXPERIMENTAL)
SET(ThisModStatus EXPERIMENTAL)
# Set whether this module has external dependencies that are not shipped with CAMPVis.
SET(ThisModExternalDependencies TRUE)
# The files and assignments need only to be parsed if the module is enabled
IF(ModuleEnabled)
# Source files:
FILE(GLOB ThisModSources RELATIVE ${ModulesDir}
modules/cppcourse/pipelines/*.cpp
modules/cppcourse/processors/*.cpp
modules/cppcourse/datastructures/*.cpp
modules/cppcourse/properties/*.cpp
modules/cppcourse/*.cpp
)
# Header files (including GLSL files so that they'll appear in VS projects)
FILE(GLOB ThisModHeaders RELATIVE ${ModulesDir}
modules/cppcourse/glsl/*.frag
modules/cppcourse/glsl/*.geom
modules/cppcourse/glsl/*.vert
modules/cppcourse/pipelines/*.h
modules/cppcourse/processors/*.h
modules/cppcourse/datastructures/*.h
modules/cppcourse/properties/*.h
)
# Define the GLSL shader path, so that all needed shaders will be deployed to target directory
SET(ThisModShaderDirectories "modules/cppcourse/glsl")
# Define dependency modules (the pipelines in the vis modules use processors from io)
#SET(ThisModDependencies io)
ENDIF(ModuleEnabled)
#include "core/pipeline/pipelinefactory.h"
#include "core/pipeline/processorfactory.h"
#include "modules/cppcourse/pipelines/cppcoursepipeline.h"
#include "modules/cppcourse/processors/verysimpleraycaster.h"
namespace campvis {
// explicitly instantiate templates to register the pipelines to make them available in the prototyping GUI
//template class PipelineRegistrar<CPPCoursePipeline>;
template class SmartProcessorRegistrar<VerySimpleRaycaster>;
}
// ================================================================================================
//
// This file is part of the CAMPVis Software Framework.
//
// If not explicitly stated otherwise: Copyright (C) 2012-2015, 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.
//
// ================================================================================================
layout(location = 0) out vec4 FragData0; ///< outgoing fragment color
#include "tools/gradient.frag"
#include "tools/raycasting.frag"
#include "tools/shading.frag"
#include "tools/texture2d.frag"
#include "tools/texture3d.frag"
#include "tools/transferfunction.frag"
uniform vec2 _viewportSizeRCP;
uniform float _jitterStepSizeMultiplier;
// ray entry points
uniform sampler2D _entryPoints;
uniform sampler2D _entryPointsDepth;
uniform TextureParameters2D _entryParams;
// ray exit points
uniform sampler2D _exitPoints;
uniform sampler2D _exitPointsDepth;
uniform TextureParameters2D _exitParams;
// DVR volume
uniform sampler3D _volume;
uniform TextureParameters3D _volumeTextureParams;
// Transfer function
uniform sampler1D _transferFunction;
uniform TFParameters1D _transferFunctionParams;
uniform LightSource _lightSource;
uniform vec3 _cameraPosition;
uniform float _samplingStepSize;
const float SAMPLING_BASE_INTERVAL_RCP = 200.0;
// The compositing mode that gets passed through from the p_compositingMode property
uniform int _compositingMode;
/**
* Performs the raycasting and returns the final fragment color.
*/
vec4 performRaycasting(in vec3 entryPoint, in vec3 exitPoint, in vec2 texCoords) {
vec4 result = vec4(0.0);
float firstHitT = -1.0;
// calculate ray start and endpoint
vec3 direction = exitPoint.rgb - entryPoint.rgb;
float t = 0.0;
float tend = length(direction);
direction = normalize(direction);
// This applies a small random offset to avoid banding artifacts
// Uncomment to see the effect ;)
jitterEntryPoint(entryPoint, direction, _samplingStepSize * _jitterStepSizeMultiplier);
while (t < tend) {
// compute sample position - next step
vec3 samplePosition = entryPoint.rgb + t * direction;
// get the intensity of our sample from the volume
float intensity = texture(_volume, samplePosition).r;
// use the transfer function to transform from
vec4 color = lookupTF(_transferFunction, _transferFunctionParams, intensity);
if (_compositingMode == 0) {
// accomodate for variable sampling rates
color.a = 1.0 - pow(1.0 - color.a, _samplingStepSize * SAMPLING_BASE_INTERVAL_RCP);
// perform compositing
// Alpha-blend the new sample with the color accumulated so far
blendUnder(result, color);
}
else if (_compositingMode == 1) {
//TODO
result = vec4(entryPoint, 1.0);
}
// else if...
// save first hit ray parameter for depth value calculation
if (firstHitT < 0.0 && result.a > 0.0) {
firstHitT = t;
}
// advance to the next evaluation point along the ray
t += _samplingStepSize;
}
// calculate depth value from ray parameter
gl_FragDepth = 1.0;
if (firstHitT >= 0.0) {
float depthEntry = texture(_entryPointsDepth, texCoords).z;
float depthExit = texture(_exitPointsDepth, texCoords).z;
gl_FragDepth = calculateDepthValue(firstHitT/tend, depthEntry, depthExit);
}
return result;
}
/***
* The main method.
***/
void main() {
vec2 p = gl_FragCoord.xy * _viewportSizeRCP;
vec3 frontPos = texture(_entryPoints, p).rgb;
vec3 backPos = texture(_exitPoints, p).rgb;
//determine whether the ray has to be casted
if (frontPos == backPos) {
//background needs no raycasting
discard;
} else {
//fragCoords are lying inside the boundingbox
FragData0 = performRaycasting(frontPos, backPos, p);
}
}
#include "cppcoursepipeline.h"
// TODO
#include "EDFhandler.h"
#include <fstream>
#include <map>
#include <exception>
#include "IOutilities.h"
Volume EDFHandler::read(const std::string& filename)
{
// open the file
std::ifstream file(filename, std::ios::binary);
if (!file.good())
throw std::invalid_argument("EDFHandler::read: error opening " + filename + " for reading");
// read a single character and make sure that a header is opened
if (file.eof() || file.get() != '{')
throw std::invalid_argument("EDFHandler::read: failed reading header opening marker");
std::map<std::string, std::string> attributes;
// --- read header data
while (!file.eof()) {
// skip whitespace
while (!file.eof()) {
const int chr = file.peek();
if (chr != '\r' && chr != '\n' && chr != ' ' && chr != '\t')
break;
file.ignore(1);
}
// abort if the header is closed
if (file.eof() || file.peek() == '}')
break;
// extract the attribute assignment
bool quotesSingle = false, quotesDouble = false;
std::string assignment = "";
while (!file.eof()) {
const int chr = file.get();
// abort on end-of-assignment
if (chr == ';' && !(quotesSingle || quotesDouble))
break;
// check for quote characters
if (chr == '\'') quotesSingle = !quotesSingle;
if (chr == '\"') quotesDouble = !quotesDouble;
assignment += chr;
}
// split the assignment, and remove quotes (if they exist)
auto delim = assignment.find('=');
if (delim == std::string::npos)
throw std::invalid_argument("EDFHandler::read: failed reading name/value delimiter");
std::string name = assignment.substr(0, delim);
StringUtils::trim(name);
std::string value = assignment.substr(delim+1);
StringUtils::trim(value);
if (value[0] == value[value.size()-1] && (value[0] == '\'' || value[0] == '\"'))
value = value.substr(1, value.size() - 2);
StringUtils::tolower(name);
attributes[name] = value;
}
file.ignore(2); // end of header marker
// --- now parse the header data
// read the dimensions
std::vector<int> dim;
for (int i = 1; ; ++i) {
// assemble the attribute name
std::stringstream aux;
aux << "dim_" << i;
// try to find the attribute
auto dimIt = attributes.find(aux.str());
if (dimIt == attributes.end())
break;
dim.push_back(StringUtils::parseString<int>(dimIt->second));
}
const int nDims = dim.size();
if (!nDims)
throw std::runtime_error("EDFHandler::read: dimension information not found");
// parse the (non-standard) spacing tag
std::vector<float> spacing;
auto spacingIt = attributes.find("spacing");
if (spacingIt != attributes.end())
StringUtils::parseString<float>(spacingIt->second, spacing);
// check for a byte order tag, but fall back to the default value
ByteOrder::ByteOrderEnum byteorder = ByteOrder::DEFAULT;
auto byteorderIt = attributes.find("byteorder");
if (byteorderIt != attributes.end()) {
std::string byteorderValue = byteorderIt->second;
StringUtils::tolower(byteorderValue);
if (byteorderValue == "highbytefirst")
byteorder = ByteOrder::HIGH_BYTE_FIRST;
else if (byteorderValue == "lowbytefirst")
byteorder = ByteOrder::LOW_BYTE_FIRST;
else
throw std::runtime_error("invalid byte order value");
}
// check for the 'element type' value
DataTypes::DataTypeEnum datatype;
auto datatypeIt = attributes.find("datatype");
if (datatypeIt != attributes.end()) {
std::string datatypeValue = datatypeIt->second;
StringUtils::tolower(datatypeValue);
if (datatypeValue == "signedbyte")
datatype = DataTypes::INT8;
else if (datatypeValue == "unsignedbyte")
datatype = DataTypes::UINT8;
else if (datatypeValue == "signedshort")
datatype = DataTypes::INT16;
else if (datatypeValue == "unsignedshort")
datatype = DataTypes::UINT16;
else if (datatypeValue == "float" || datatypeValue == "floatvalue" || datatypeValue == "real")
datatype = DataTypes::FLOAT32;
else if (datatypeValue == "double" || datatypeValue == "doublevalue")
datatype = DataTypes::FLOAT64;
// todo: add further data types
else
throw std::runtime_error("EDFHandler::read: invalid/unsupported data type");
}
else
throw std::runtime_error("EDFHandler::read: data type not found");
auto compressionIt = attributes.find("compression");
if (compressionIt != attributes.end())
throw std::runtime_error("compression not supported");
int size = 0;
auto sizeIt = attributes.find("size");
if (sizeIt != attributes.end())
size = StringUtils::parseString<int>(sizeIt->second);
auto imageIt = attributes.find("image");
if (imageIt != attributes.end() && StringUtils::parseString<int>(imageIt->second) != 1)
throw std::runtime_error("EDFHandler::read: image not set to 1");
// convert size
Eigen::VectorXi dimSizes(nDims);
for (int i = 0; i < nDims; ++i)
dimSizes[i] = dim[i];
if (dimSizes.prod() * DataTypes::getSizeForType(datatype) != size)
throw std::runtime_error("EDFHandler::read: size inconsistency");
// convert spacing
Eigen::VectorXf dimSpacing = Eigen::VectorXf::Ones(nDims);
if (spacing.size() > 0) {
if (spacing.size() != nDims)
throw std::runtime_error("EDFHandler::read: spacing inconsistency");
for (int i = 0; i < nDims; ++i)
dimSpacing[i] = spacing[i];
}
// compute the byte shuffling flag
const bool byteShuffle = (byteorder != ByteOrder::DEFAULT);
// --- read in the image data
Eigen::VectorXf data = DataTypes::parseData<float>(file, datatype, dimSizes.prod(), byteShuffle);
file.close();
// --- setup the volume
Eigen::Vector3f lowerLeft = Eigen::Vector3f::Zero();
Eigen::Vector3f upperRight = lowerLeft + (dimSizes.cast<float>().array() * dimSpacing.array() ).matrix();
Volume vol(lowerLeft, upperRight, dimSpacing);
vol.setContent(data);
return vol;
}
void EDFHandler::write(const std::string& filename, const Volume& vol)
{
// open the EDF file
std::ofstream file(filename, std::ios::binary);
if (!file.good())
throw std::invalid_argument("EDFHandler::write: cannot open " + filename + " for writing");
// open the header
file << "{\n";
file << "HeaderID = EH:000001:000000:000000;\n";
file << "Image = " << 1 << ";\n";
if (ByteOrder::DEFAULT == ByteOrder::HIGH_BYTE_FIRST)
file << "ByteOrder = HighByteFirst;\n";
else
file << "ByteOrder = LowByteFirst;\n";
file << "DataType = " << TYPE_FLOAT << ";\n";
// write dimension and size
for (int i = 0; i < 3; ++i)
file << "Dim_" << (i+1) << " = " << vol.getNumVoxels()[i] << ";\n";
file << "Size = " << vol.getNumVoxels().prod() * sizeof(float) << ";\n";
// write spacing
file << "Spacing =";
for (int i = 0; i < 3; ++i)
file << ' ' << vol.getSpacing()[i];
file << ";\n";
// pad the header by adding spaces such that the header ends on a kilobyte boundary
std::size_t n = 1024;
while (n < (static_cast<std::size_t>(file.tellp()) + 3))
n += 1024;
n -= static_cast<std::size_t>(file.tellp()) + 3;
while (n > 0) {
file.put(' ');
n--;
}
// close the header
file << "\n}\n";
// write the volume data
file.write(reinterpret_cast<const char*>(vol.getContent().data()), vol.getNumVoxels().prod() * sizeof(float) );
file.close();
}
#ifndef EDFHANDLER_H
#define EDFHANDLER_H
#include <string>
#include "volume.h"
/**
* \brief EDFHandler reads and writes EDF files.
*/
class EDFHandler {
public:
/* \brief read an EDF file into a Volume
* \param[in] filename - filename of the EDF file to be read
* \return volume containing the data
*/
static Volume read(const std::string& filename);
/* \brief write out a Volume into an EDF file
* \param[in] filename - filename of the EDF file to be written
* \param[in] vol - volume to be written
*/
static void write(const std::string& filename, const Volume& vol);
private:
static constexpr auto TYPE_FLOAT = "FloatValue";
};
#endif // EDFHANDLER_H
#ifndef IOUTILITIES_H
#define IOUTILITIES_H
#include <algorithm>
#include <regex>
#include <string>
#include <vector>
#include <Eigen/Dense>
/**
* \brief StringUtils provides common string utilities for file parsing.
*/
struct StringUtils {
/* \brief remove whitespace at front and back of string
* \param[in,out] str - string to be trimmed
*/
static void trim(std::string& str);
/* \brief convert string to lower case
* \param[in,out] str - string to be converted
*/
static void tolower(std::string& str);
/* \brief convert string to upper case
* \param[in,out] str - string to be converted
*/
static void toupper(std::string& str);
/* \brief convert string to arbitrary datatype using stringstream
* \param[in] str - string to be converted
* \return converted datatype
*/
template<typename Type>
static Type parseString(const std::string& str);
/* \brief convert string to vector of arbitrary data type using stringstream
* \param[in] str - string to be converted
* \param[out] vec - vector of converted datatypes
* \return number of converted datatypes
*/
template<typename Type>
static int parseString(const std::string& str, std::vector<Type>& vec);
};
/**
* \brief ByteOrder supplies helper routines to cope with byte order conversions.
*/
struct ByteOrder {
/// byte order enum
enum ByteOrderEnum {
LOW_BYTE_FIRST, // little endian (e.g. x86)
HIGH_BYTE_FIRST // big endian (e.g. PowerPC)
};
/// attempt to determine endianness (works on Mac at least..), default to little endian
static const ByteOrderEnum DEFAULT =
#if defined(BYTE_ORDER) && defined(BIG_ENDIAN) && BYTE_ORDER == BIG_ENDIAN
HIGH_BYTE_FIRST;
#else
LOW_BYTE_FIRST;
#endif
/* \brief shuffle the bytes of the data pointed to by ptr to switch endianness
* \tparam _DataType - data type underlying the pointer
* \param[in,out] ptr - pointer to the data
* \param[in] elements - number of elements of _DataType
*/
template<typename _DataType>
static void shuffleBytes(void* ptr, long elements);
};
/**
* \brief DataTypes supplies helper enums and routines for type conversions.
*/
struct DataTypes {
/// data type enum
enum DataTypeEnum {
INT8, UINT8,
INT16, UINT16,
INT32, UINT32,
FLOAT32,
FLOAT64
};
/* \brief return the size in bytes for the specified data type
* \param[in] dataType - type for which to return the size in bytes
* \return size of dataType in bytes
*/
static int getSizeForType(const DataTypeEnum& dataType);
/* \brief parse data of type dataType from stream in into an Eigen::Vector
* (dispatches to parseData<_DataType, _Scalar> internally)
* \tparam _Scalar - scalar type of Eigen::Vector to return
* \param[in] in - stream from which to read
* \param[in] dataType - dataType of elements to be read from stream
* \param[in] sizeInElements - number of elements (of type dataType) to read from stream
* \param[in] shuffleBytes - flag whether to shuffle bytes around to account for endianness change
* \return vector of _Scalar containing the read in values
*/
template<typename _Scalar>
static Eigen::Matrix<_Scalar, Eigen::Dynamic, 1> parseData(std::istream& in, const DataTypeEnum& dataType, long sizeInElements, bool shuffleBytes);
private:
/* \brief parse data of type _DataType from stream in into an Eigen::Vector
* \tparam _DataType - data type of elements to be read from stream
* \tparam _Scalar - scalar type of Eigen::Vector to return
* \param[in] in - stream from which to read
* \param[in] sizeInElements - number of elements (of type _DataType) to read from stream
* \param[in] shuffleBytes - flag whether to shuffle bytes around to account for endianness change
* \return vector of _Scalar containing the read in values
*/
template<typename _DataType, typename _Scalar>
static Eigen::Matrix<_Scalar, Eigen::Dynamic, 1> parseData(std::istream& in, long sizeInElements, bool shuffleBytes);
};
// --------- Implementations -----------
inline void StringUtils::trim(std::string& str)
{
str = std::regex_replace(str, std::regex("^\\s+|\\s+$"), std::string(""));
}
inline void StringUtils::tolower(std::string& str)
{
std::transform(str.begin(), str.end(), str.begin(), ::tolower);
}
inline void StringUtils::toupper(std::string& str)
{
std::transform(str.begin(), str.end(), str.begin(), ::toupper);
}