From 9b355f652414e6cfe6521d851acbdfaf76db1c2308f9b4ea646ebaa22f6ddfed Mon Sep 17 00:00:00 2001 From: Mexpert_PRO Date: Thu, 26 Mar 2026 21:04:54 +0100 Subject: [PATCH] added transparency and LUT lookup --- TSE-RTS/Resources/DCMVizPresets/Preset1.json | 49 ++++ TSE-RTS/src/elements/visualizationData.cpp | 283 +++++++++++++++++++ TSE-RTS/src/elements/visualizationData.hpp | 33 +++ TSE-RTS/src/game.cpp | 20 +- TSE-RTS/src/shaders/DICOMShader.cpp | 9 +- TSE-RTS/src/shaders/DICOMShader.hpp | 2 + TSE-RTS/src/shaders/DICOMShaderGLSL.hpp | 38 ++- 7 files changed, 416 insertions(+), 18 deletions(-) create mode 100644 TSE-RTS/Resources/DCMVizPresets/Preset1.json create mode 100644 TSE-RTS/src/elements/visualizationData.cpp create mode 100644 TSE-RTS/src/elements/visualizationData.hpp diff --git a/TSE-RTS/Resources/DCMVizPresets/Preset1.json b/TSE-RTS/Resources/DCMVizPresets/Preset1.json new file mode 100644 index 0000000..19b0213 --- /dev/null +++ b/TSE-RTS/Resources/DCMVizPresets/Preset1.json @@ -0,0 +1,49 @@ +{ + "transparencyCount": 4, + "transparency": [ + { + "index": 0.3114, + "transpValue": 0.0 + }, + { + "index": 0.3233, + "transpValue": 1.0 + }, + { + "index": 0.5431, + "transpValue": 1.0 + }, + { + "index": 0.5675, + "transpValue": 0.0 + } + ], + + "colorCount": 6, + "colors": [ + { + "index": 0.0000, + "color": { "x": 0.00, "y": 0.00, "z": 0.00 } + }, + { + "index": 0.2256, + "color": { "x": 0.10, "y": 0.08, "z": 0.08 } + }, + { + "index": 0.2598, + "color": { "x": 0.30, "y": 0.22, "z": 0.20 } + }, + { + "index": 0.3233, + "color": { "x": 0.92, "y": 0.88, "z": 0.78 } + }, + { + "index": 0.4454, + "color": { "x": 0.96, "y": 0.92, "z": 0.84 } + }, + { + "index": 0.5431, + "color": { "x": 1.00, "y": 0.98, "z": 0.92 } + } + ] +} \ No newline at end of file diff --git a/TSE-RTS/src/elements/visualizationData.cpp b/TSE-RTS/src/elements/visualizationData.cpp new file mode 100644 index 0000000..712aec0 --- /dev/null +++ b/TSE-RTS/src/elements/visualizationData.cpp @@ -0,0 +1,283 @@ +#include "visualizationData.hpp" +#include "json.hpp" +#include "utils/JsonExports.hpp" + +#include +#include +#include +#include +#include + +namespace +{ + constexpr int LUT_WIDTH = 256; + constexpr int LUT_HEIGHT = 256; + constexpr int LUT_PIXEL_COUNT = LUT_WIDTH * LUT_HEIGHT; + constexpr float LUT_INV_MAX_INDEX = 1.0f / static_cast(LUT_PIXEL_COUNT - 1); + + inline float Clamp01(const float value) + { + return std::min(1.0f, std::max(0.0f, value)); + } + + inline float Lerp(const float a, const float b, const float t) + { + return a + (b - a) * t; + } +} + +VisualizationData VisualizationData::LoadVisualizationSetting(string path) +{ + VisualizationData dataOut{}; + namespace fs = std::filesystem; + + const fs::path jsonPath = fs::absolute(path); + if(!fs::exists(jsonPath) || !fs::is_regular_file(jsonPath)) + { + return dataOut; + } + + std::ifstream in(jsonPath, std::ios::in | std::ios::binary); + if(!in.is_open()) + { + return dataOut; + } + + nlohmann::json json = nlohmann::json::parse(in, nullptr, false); + if(json.is_discarded() || !json.is_object()) + { + return dataOut; + } + + const auto transpIt = json.find("transparency"); + const auto colorsIt = json.find("colors"); + + const std::size_t transpArrayCount = (transpIt != json.end() && transpIt->is_array()) ? transpIt->size() : 0; + const std::size_t colorArrayCount = (colorsIt != json.end() && colorsIt->is_array()) ? colorsIt->size() : 0; + + const std::size_t transpCount = std::min(json.value("transparencyCount", transpArrayCount), transpArrayCount); + const std::size_t colorCount = std::min(json.value("colorCount", colorArrayCount), colorArrayCount); + + dataOut.transparencyCount = transpCount; + dataOut.colorCount = colorCount; + + if(transpCount > 0) + { + dataOut.transparency = new TransparencyData[transpCount](); + for(std::size_t i = 0; i < transpCount; ++i) + { + const nlohmann::json& point = (*transpIt)[i]; + dataOut.transparency[i].index = point.value("index", 0.0f); + dataOut.transparency[i].transpValue = point.value("transpValue", 0.0f); + } + } + + if(colorCount > 0) + { + dataOut.colors = new ColorData[colorCount](); + for(std::size_t i = 0; i < colorCount; ++i) + { + const nlohmann::json& point = (*colorsIt)[i]; + dataOut.colors[i].index = point.value("index", 0.0f); + const auto colorIt = point.find("color"); + if(colorIt != point.end() && colorIt->is_object() && + colorIt->contains("x") && colorIt->contains("y") && colorIt->contains("z")) + { + dataOut.colors[i].color = TSE::ImportVector3(*colorIt); + } + } + } + + return dataOut; +} + +void VisualizationData::FillLUTTexture(Texture& texture) const +{ + if(static_cast(texture.Width()) != LUT_WIDTH || static_cast(texture.Height()) != LUT_HEIGHT) + { + return; + } + + std::vector transparencyPoints; + std::vector colorPoints; + + if(transparency != nullptr && transparencyCount > 0) + { + transparencyPoints.assign(transparency, transparency + transparencyCount); + std::sort(transparencyPoints.begin(), transparencyPoints.end(), [](const TransparencyData& a, const TransparencyData& b) + { + return a.index < b.index; + }); + } + + if(colors != nullptr && colorCount > 0) + { + colorPoints.assign(colors, colors + colorCount); + std::sort(colorPoints.begin(), colorPoints.end(), [](const ColorData& a, const ColorData& b) + { + return a.index < b.index; + }); + } + + std::size_t transparencySegment = 0; + std::size_t colorSegment = 0; + + if(texture.bpp() == 32) + { + byte* pixel = texture.GetImagePtr(); + if(pixel == nullptr) + { + return; + } + + for(int i = 0; i < LUT_PIXEL_COUNT; ++i) + { + const float value = static_cast(i) * LUT_INV_MAX_INDEX; + + float alpha = 1.0f; + if(!transparencyPoints.empty()) + { + if(transparencyPoints.size() == 1 || value <= transparencyPoints.front().index) + { + alpha = transparencyPoints.front().transpValue; + } + else if(value >= transparencyPoints.back().index) + { + alpha = transparencyPoints.back().transpValue; + } + else + { + while(transparencySegment + 1 < transparencyPoints.size() && + value > transparencyPoints[transparencySegment + 1].index) + { + ++transparencySegment; + } + + const TransparencyData& p0 = transparencyPoints[transparencySegment]; + const TransparencyData& p1 = transparencyPoints[transparencySegment + 1]; + const float range = p1.index - p0.index; + const float t = (std::abs(range) > 1e-7f) ? Clamp01((value - p0.index) / range) : 0.0f; + alpha = Lerp(p0.transpValue, p1.transpValue, t); + } + } + + float r = 0.0f; + float g = 0.0f; + float b = 0.0f; + if(!colorPoints.empty()) + { + if(colorPoints.size() == 1 || value <= colorPoints.front().index) + { + r = colorPoints.front().color.x; + g = colorPoints.front().color.y; + b = colorPoints.front().color.z; + } + else if(value >= colorPoints.back().index) + { + r = colorPoints.back().color.x; + g = colorPoints.back().color.y; + b = colorPoints.back().color.z; + } + else + { + while(colorSegment + 1 < colorPoints.size() && + value > colorPoints[colorSegment + 1].index) + { + ++colorSegment; + } + + const ColorData& p0 = colorPoints[colorSegment]; + const ColorData& p1 = colorPoints[colorSegment + 1]; + const float range = p1.index - p0.index; + const float t = (std::abs(range) > 1e-7f) ? Clamp01((value - p0.index) / range) : 0.0f; + + r = Lerp(p0.color.x, p1.color.x, t); + g = Lerp(p0.color.y, p1.color.y, t); + b = Lerp(p0.color.z, p1.color.z, t); + } + } + + *pixel++ = static_cast(Clamp01(b) * 255.0f); + *pixel++ = static_cast(Clamp01(g) * 255.0f); + *pixel++ = static_cast(Clamp01(r) * 255.0f); + *pixel++ = static_cast(Clamp01(alpha) * 255.0f); + } + + texture.Apply(); + return; + } + + for(int i = 0; i < LUT_PIXEL_COUNT; ++i) + { + const float value = static_cast(i) * LUT_INV_MAX_INDEX; + + float alpha = 1.0f; + if(!transparencyPoints.empty()) + { + if(transparencyPoints.size() == 1 || value <= transparencyPoints.front().index) + { + alpha = transparencyPoints.front().transpValue; + } + else if(value >= transparencyPoints.back().index) + { + alpha = transparencyPoints.back().transpValue; + } + else + { + while(transparencySegment + 1 < transparencyPoints.size() && + value > transparencyPoints[transparencySegment + 1].index) + { + ++transparencySegment; + } + + const TransparencyData& p0 = transparencyPoints[transparencySegment]; + const TransparencyData& p1 = transparencyPoints[transparencySegment + 1]; + const float range = p1.index - p0.index; + const float t = (std::abs(range) > 1e-7f) ? Clamp01((value - p0.index) / range) : 0.0f; + alpha = Lerp(p0.transpValue, p1.transpValue, t); + } + } + + float r = 0.0f; + float g = 0.0f; + float b = 0.0f; + if(!colorPoints.empty()) + { + if(colorPoints.size() == 1 || value <= colorPoints.front().index) + { + r = colorPoints.front().color.x; + g = colorPoints.front().color.y; + b = colorPoints.front().color.z; + } + else if(value >= colorPoints.back().index) + { + r = colorPoints.back().color.x; + g = colorPoints.back().color.y; + b = colorPoints.back().color.z; + } + else + { + while(colorSegment + 1 < colorPoints.size() && + value > colorPoints[colorSegment + 1].index) + { + ++colorSegment; + } + + const ColorData& p0 = colorPoints[colorSegment]; + const ColorData& p1 = colorPoints[colorSegment + 1]; + const float range = p1.index - p0.index; + const float t = (std::abs(range) > 1e-7f) ? Clamp01((value - p0.index) / range) : 0.0f; + + r = Lerp(p0.color.x, p1.color.x, t); + g = Lerp(p0.color.y, p1.color.y, t); + b = Lerp(p0.color.z, p1.color.z, t); + } + } + + const int x = (i & 255); + const int y = (i >> 8); + texture.SetPixelNoApply(x, y, Color(Clamp01(r), Clamp01(g), Clamp01(b), Clamp01(alpha))); + } + + texture.Apply(); +} diff --git a/TSE-RTS/src/elements/visualizationData.hpp b/TSE-RTS/src/elements/visualizationData.hpp new file mode 100644 index 0000000..2e31607 --- /dev/null +++ b/TSE-RTS/src/elements/visualizationData.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include "Types.hpp" +#include "Vector3.hpp" +#include "elements/Texture.hpp" +#include + +using namespace TSE; + +struct TransparencyData +{ + float index; + float transpValue; +}; + +struct ColorData +{ + Vector3 color; + float index; +}; + + +struct VisualizationData +{ + public: + TransparencyData* transparency = nullptr; + ColorData* colors = nullptr; + std::size_t transparencyCount = 0; + std::size_t colorCount = 0; + + static VisualizationData LoadVisualizationSetting(string path); + void FillLUTTexture(Texture& texture) const; +}; diff --git a/TSE-RTS/src/game.cpp b/TSE-RTS/src/game.cpp index d5442e7..e6a3d97 100644 --- a/TSE-RTS/src/game.cpp +++ b/TSE-RTS/src/game.cpp @@ -16,6 +16,7 @@ #include "BehaviourScripts/CanvasScaler.hpp" #include "Random.hpp" #include "elements/VolumeTexture3D.hpp" +#include "elements/visualizationData.hpp" #define circleRadius 32 #define circleFallof 25 @@ -149,7 +150,11 @@ void game::setup(TSE::Scene* s, TSE::IWindow* wnd) s->AddLayer(&renderingLayer); - float smallDicomDevider = 8.0f; + float smallDicomDevider = 16.0f; + + Texture* lutTexture = new Texture(256, 256, 32); + VisualizationData data = VisualizationData::LoadVisualizationSetting("./DCMVizPresets/Preset1.json"); + data.FillLUTTexture(*lutTexture); VolumeTexture3D* dicom = new VolumeTexture3D("./DCM"); Vector3 textureSize = {dicom->Width(), dicom->Height(), dicom->Depth()}; @@ -170,11 +175,16 @@ void game::setup(TSE::Scene* s, TSE::IWindow* wnd) for(int sz = 0; sz < smallDicomDevider; sz++) { Color c; - Vector3 pos = Vector3(x * 8 + sx, y * 8 + sy, z * 8 + sz); + Vector3 pos = Vector3(x * smallDicomDevider + sx, y * smallDicomDevider + sy, z * smallDicomDevider + sz); if(pos.x < dicom->Width() && pos.y < dicom->Height() && pos.z < dicom->Depth()) { dicom->GetPixel(pos, c); - value = fmax(value, c.r); + ushort index = (ushort)(c.r * 65536.0f); + int x = index & 0xFF; + int y = index >> 8; + + lutTexture->GetPixel(x, y, c); + value = fmax(c.a, value); } } } @@ -185,8 +195,6 @@ void game::setup(TSE::Scene* s, TSE::IWindow* wnd) } dicomSmall->Apply(); - - //s->AddLayer(&characterLayer); Transformable* player = new Transformable("Player"); @@ -256,12 +264,14 @@ void game::setup(TSE::Scene* s, TSE::IWindow* wnd) Material* canvasMat = new Material("canvasMat", ShaderRegistry::GetShader("DICOMShader")); canvasMat->SetValue("threshold", 0.4f); canvasMat->SetValue("stepSize", 0.01f); + canvasMat->SetValue("brickSize", smallDicomDevider); canvasMat->SetValue("texSize", textureSize); canvasMat->SetValue("smallTexSize", smallTextureSize); Vector3 objectScale = Vector3(1,1,1); canvasMat->SetValue("ObjectScale", objectScale); canvasMat->SetValue("DICOM", dicom); canvasMat->SetValue("DICOMsmall", dicomSmall); + canvasMat->SetValue("LUT", lutTexture); // canvasMat->SetValue("heightTextureID", rt->GetTextureId(1)); // canvasMat->SetValue("depthTextureID", rt->GetTextureId(2)); // canvasMat->SetValue("colorTexture2ID", rtProps->GetTextureId(0)); diff --git a/TSE-RTS/src/shaders/DICOMShader.cpp b/TSE-RTS/src/shaders/DICOMShader.cpp index ee7f02e..401b3a4 100644 --- a/TSE-RTS/src/shaders/DICOMShader.cpp +++ b/TSE-RTS/src/shaders/DICOMShader.cpp @@ -36,6 +36,7 @@ void DICOMShader::Init(float width, float height) instance->Enable(); instance->SetUniform("DICOMTexture", 0); instance->SetUniform("SmallDICOMTexture", 1); + instance->SetUniform("LUTTexture", 2); instance->SetUniform("Threshold", 0.5f); instance->SetUniform("TexSize", &Vector3::zero); instance->SetUniform("SmallTexSize", &Vector3::zero); @@ -68,13 +69,15 @@ void DICOMShader::OnFlush() glBindTexture(GL_TEXTURE_3D, DICOM->GetTextureId()); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_3D, SmallDICOM->GetTextureId()); + glActiveTexture(GL_TEXTURE2); + glBindTexture(GL_TEXTURE_2D, LUT->GetTextureId()); SetUniform("Threshold", Threshold); if(stepSize < 0.001f) stepSize = 0.001f; SetUniform("StepSize", stepSize); SetUniform("TexSize", &size); SetUniform("SmallTexSize", &smallSize); SetUniform("ObjectScale", &scale); - SetUniform("BrickScale", 8.0f); + SetUniform("BrickScale", brickSize); } void DICOMShader::OnDrawCall(int indexCount) @@ -93,12 +96,16 @@ void DICOMShader::OnSubmit(const TSE::Transformable &t, float *&target, TSE::Tra if(!r->GetMaterial()->HasValue("DICOMsmall")) return; if(!r->GetMaterial()->HasValue("stepSize")) return; if(!r->GetMaterial()->HasValue("ObjectScale")) return; + if(!r->GetMaterial()->HasValue("LUT")) return; + if(!r->GetMaterial()->HasValue("brickSize")) return; Threshold = r->GetMaterial()->GetValue("threshold"); stepSize = r->GetMaterial()->GetValue("stepSize"); + brickSize = r->GetMaterial()->GetValue("brickSize"); size = r->GetMaterial()->GetValue("texSize"); smallSize = r->GetMaterial()->GetValue("smallTexSize"); scale = r->GetMaterial()->GetValue("ObjectScale"); DICOM = r->GetMaterial()->GetValue("DICOM"); + LUT = r->GetMaterial()->GetValue("LUT"); SmallDICOM = r->GetMaterial()->GetValue("DICOMsmall"); const Vector3* verts = r->GetVertices(); diff --git a/TSE-RTS/src/shaders/DICOMShader.hpp b/TSE-RTS/src/shaders/DICOMShader.hpp index ac0dd8a..670d2d7 100644 --- a/TSE-RTS/src/shaders/DICOMShader.hpp +++ b/TSE-RTS/src/shaders/DICOMShader.hpp @@ -12,11 +12,13 @@ private: static DICOMShader* instance; TSE::ITexture* DICOM; TSE::ITexture* SmallDICOM; + TSE::ITexture* LUT; TSE::Vector3 size; TSE::Vector3 smallSize; TSE::Vector3 scale; float Threshold; float stepSize; + float brickSize; public: static DICOMShader* Instance(); diff --git a/TSE-RTS/src/shaders/DICOMShaderGLSL.hpp b/TSE-RTS/src/shaders/DICOMShaderGLSL.hpp index 7794205..3c4547a 100644 --- a/TSE-RTS/src/shaders/DICOMShaderGLSL.hpp +++ b/TSE-RTS/src/shaders/DICOMShaderGLSL.hpp @@ -28,6 +28,7 @@ inline const char* fragDICOM = R"( uniform sampler3D DICOMTexture; uniform sampler3D SmallDICOMTexture; + uniform sampler2D LUTTexture; uniform vec3 CamPos; uniform vec3 CamRight; @@ -49,6 +50,8 @@ inline const char* fragDICOM = R"( uniform float BrickScale; + const float EPSILON = 1e-6; + in DATA { vec2 uv_out; @@ -85,7 +88,7 @@ inline const char* fragDICOM = R"( rayDir = CamForward; } - float TraverseFineBlock(vec3 rayOrigin, vec3 rayDir, float blockTEnter, float blockTExit, ivec3 brickCoord, float brickScaleF, ivec3 gridSize) + vec4 TraverseFineBlock(vec3 rayOrigin, vec3 rayDir, float blockTEnter, float blockTExit, ivec3 brickCoord, float brickScaleF, ivec3 gridSize, vec4 currColor) { int brickSize = int(brickScaleF); @@ -120,16 +123,25 @@ inline const char* fragDICOM = R"( int maxIters = brickSize * 3 + 8; - float hits = 0; - for (int i = 0; i < maxIters; ++i) { if (t > blockTExit) break; float density = texelFetch(DICOMTexture, voxel, 0).r; - if (density >= Threshold) - hits++; + uint v = uint(density * 65535.0 + 0.5); + uint x = v & 0xFFu; + uint y = v >> 8u; + + vec4 lutValue = texelFetch(LUTTexture, ivec2(x, y), 0); + if (lutValue.a > EPSILON) + { + currColor.xyz += (1.0 - currColor.w) * lutValue.rgb * lutValue.a; + currColor.w += (1.0 - currColor.w) * lutValue.a; + + if (currColor.w > 0.98) + break; + } if (tMax.x <= tMax.y && tMax.x <= tMax.z) { @@ -154,7 +166,7 @@ inline const char* fragDICOM = R"( } } - return hits; + return currColor; } vec4 RenderVoxelDDA(vec3 rayOrigin, vec3 rayDir) @@ -211,6 +223,8 @@ inline const char* fragDICOM = R"( float increment = StepSize; + vec4 colorAcum = vec4(0.0); + for (int i = 0; i < maxIters; ++i) { if (t > tExit) @@ -221,11 +235,12 @@ inline const char* fragDICOM = R"( float cellTExit = min(min(tMax.x, tMax.y), tMax.z); cellTExit = min(cellTExit, tExit); - if (brickDensity >= Threshold) + if (brickDensity >= EPSILON) { - float hits = TraverseFineBlock(rayOrigin, rayDir, t, cellTExit, brick, brickScaleF, gridSize); - count += hits * increment; - if(count >= 1) break; + colorAcum = TraverseFineBlock(rayOrigin, rayDir, t, cellTExit, brick, brickScaleF, gridSize, colorAcum); + + if (colorAcum.w > 0.98) + break; } if (tMax.x <= tMax.y && tMax.x <= tMax.z) @@ -250,9 +265,8 @@ inline const char* fragDICOM = R"( tMax.z += tDelta.z; } } - count = clamp(count, 0.0, 1.0); - return vec4(count, count, count, 1.0); + return vec4(colorAcum.x, colorAcum.y, colorAcum.z, 1.0); } void main()