added transparency and LUT lookup

This commit is contained in:
2026-03-26 21:04:54 +01:00
parent 51e4112868
commit 9b355f6524
7 changed files with 416 additions and 18 deletions

View File

@@ -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 }
}
]
}

View File

@@ -0,0 +1,283 @@
#include "visualizationData.hpp"
#include "json.hpp"
#include "utils/JsonExports.hpp"
#include <algorithm>
#include <cmath>
#include <filesystem>
#include <fstream>
#include <vector>
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<float>(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<int>(texture.Width()) != LUT_WIDTH || static_cast<int>(texture.Height()) != LUT_HEIGHT)
{
return;
}
std::vector<TransparencyData> transparencyPoints;
std::vector<ColorData> 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<float>(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<byte>(Clamp01(b) * 255.0f);
*pixel++ = static_cast<byte>(Clamp01(g) * 255.0f);
*pixel++ = static_cast<byte>(Clamp01(r) * 255.0f);
*pixel++ = static_cast<byte>(Clamp01(alpha) * 255.0f);
}
texture.Apply();
return;
}
for(int i = 0; i < LUT_PIXEL_COUNT; ++i)
{
const float value = static_cast<float>(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();
}

View File

@@ -0,0 +1,33 @@
#pragma once
#include "Types.hpp"
#include "Vector3.hpp"
#include "elements/Texture.hpp"
#include <cstddef>
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;
};

View File

@@ -16,6 +16,7 @@
#include "BehaviourScripts/CanvasScaler.hpp" #include "BehaviourScripts/CanvasScaler.hpp"
#include "Random.hpp" #include "Random.hpp"
#include "elements/VolumeTexture3D.hpp" #include "elements/VolumeTexture3D.hpp"
#include "elements/visualizationData.hpp"
#define circleRadius 32 #define circleRadius 32
#define circleFallof 25 #define circleFallof 25
@@ -149,7 +150,11 @@ void game::setup(TSE::Scene* s, TSE::IWindow* wnd)
s->AddLayer(&renderingLayer); 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"); VolumeTexture3D* dicom = new VolumeTexture3D("./DCM");
Vector3 textureSize = {dicom->Width(), dicom->Height(), dicom->Depth()}; 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++) for(int sz = 0; sz < smallDicomDevider; sz++)
{ {
Color c; 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()) if(pos.x < dicom->Width() && pos.y < dicom->Height() && pos.z < dicom->Depth())
{ {
dicom->GetPixel(pos, c); 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(); dicomSmall->Apply();
//s->AddLayer(&characterLayer); //s->AddLayer(&characterLayer);
Transformable* player = new Transformable("Player"); 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")); Material* canvasMat = new Material("canvasMat", ShaderRegistry::GetShader("DICOMShader"));
canvasMat->SetValue<float>("threshold", 0.4f); canvasMat->SetValue<float>("threshold", 0.4f);
canvasMat->SetValue<float>("stepSize", 0.01f); canvasMat->SetValue<float>("stepSize", 0.01f);
canvasMat->SetValue<float>("brickSize", smallDicomDevider);
canvasMat->SetValue<Vector3>("texSize", textureSize); canvasMat->SetValue<Vector3>("texSize", textureSize);
canvasMat->SetValue<Vector3>("smallTexSize", smallTextureSize); canvasMat->SetValue<Vector3>("smallTexSize", smallTextureSize);
Vector3 objectScale = Vector3(1,1,1); Vector3 objectScale = Vector3(1,1,1);
canvasMat->SetValue<Vector3>("ObjectScale", objectScale); canvasMat->SetValue<Vector3>("ObjectScale", objectScale);
canvasMat->SetValue<ITexture*>("DICOM", dicom); canvasMat->SetValue<ITexture*>("DICOM", dicom);
canvasMat->SetValue<ITexture*>("DICOMsmall", dicomSmall); canvasMat->SetValue<ITexture*>("DICOMsmall", dicomSmall);
canvasMat->SetValue<ITexture*>("LUT", lutTexture);
// canvasMat->SetValue<uint>("heightTextureID", rt->GetTextureId(1)); // canvasMat->SetValue<uint>("heightTextureID", rt->GetTextureId(1));
// canvasMat->SetValue<uint>("depthTextureID", rt->GetTextureId(2)); // canvasMat->SetValue<uint>("depthTextureID", rt->GetTextureId(2));
// canvasMat->SetValue<uint>("colorTexture2ID", rtProps->GetTextureId(0)); // canvasMat->SetValue<uint>("colorTexture2ID", rtProps->GetTextureId(0));

View File

@@ -36,6 +36,7 @@ void DICOMShader::Init(float width, float height)
instance->Enable(); instance->Enable();
instance->SetUniform("DICOMTexture", 0); instance->SetUniform("DICOMTexture", 0);
instance->SetUniform("SmallDICOMTexture", 1); instance->SetUniform("SmallDICOMTexture", 1);
instance->SetUniform("LUTTexture", 2);
instance->SetUniform("Threshold", 0.5f); instance->SetUniform("Threshold", 0.5f);
instance->SetUniform("TexSize", &Vector3::zero); instance->SetUniform("TexSize", &Vector3::zero);
instance->SetUniform("SmallTexSize", &Vector3::zero); instance->SetUniform("SmallTexSize", &Vector3::zero);
@@ -68,13 +69,15 @@ void DICOMShader::OnFlush()
glBindTexture(GL_TEXTURE_3D, DICOM->GetTextureId()); glBindTexture(GL_TEXTURE_3D, DICOM->GetTextureId());
glActiveTexture(GL_TEXTURE1); glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_3D, SmallDICOM->GetTextureId()); glBindTexture(GL_TEXTURE_3D, SmallDICOM->GetTextureId());
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, LUT->GetTextureId());
SetUniform("Threshold", Threshold); SetUniform("Threshold", Threshold);
if(stepSize < 0.001f) stepSize = 0.001f; if(stepSize < 0.001f) stepSize = 0.001f;
SetUniform("StepSize", stepSize); SetUniform("StepSize", stepSize);
SetUniform("TexSize", &size); SetUniform("TexSize", &size);
SetUniform("SmallTexSize", &smallSize); SetUniform("SmallTexSize", &smallSize);
SetUniform("ObjectScale", &scale); SetUniform("ObjectScale", &scale);
SetUniform("BrickScale", 8.0f); SetUniform("BrickScale", brickSize);
} }
void DICOMShader::OnDrawCall(int indexCount) 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("DICOMsmall")) return;
if(!r->GetMaterial()->HasValue("stepSize")) return; if(!r->GetMaterial()->HasValue("stepSize")) return;
if(!r->GetMaterial()->HasValue("ObjectScale")) return; if(!r->GetMaterial()->HasValue("ObjectScale")) return;
if(!r->GetMaterial()->HasValue("LUT")) return;
if(!r->GetMaterial()->HasValue("brickSize")) return;
Threshold = r->GetMaterial()->GetValue<float>("threshold"); Threshold = r->GetMaterial()->GetValue<float>("threshold");
stepSize = r->GetMaterial()->GetValue<float>("stepSize"); stepSize = r->GetMaterial()->GetValue<float>("stepSize");
brickSize = r->GetMaterial()->GetValue<float>("brickSize");
size = r->GetMaterial()->GetValue<Vector3>("texSize"); size = r->GetMaterial()->GetValue<Vector3>("texSize");
smallSize = r->GetMaterial()->GetValue<Vector3>("smallTexSize"); smallSize = r->GetMaterial()->GetValue<Vector3>("smallTexSize");
scale = r->GetMaterial()->GetValue<Vector3>("ObjectScale"); scale = r->GetMaterial()->GetValue<Vector3>("ObjectScale");
DICOM = r->GetMaterial()->GetValue<ITexture*>("DICOM"); DICOM = r->GetMaterial()->GetValue<ITexture*>("DICOM");
LUT = r->GetMaterial()->GetValue<ITexture*>("LUT");
SmallDICOM = r->GetMaterial()->GetValue<ITexture*>("DICOMsmall"); SmallDICOM = r->GetMaterial()->GetValue<ITexture*>("DICOMsmall");
const Vector3* verts = r->GetVertices(); const Vector3* verts = r->GetVertices();

View File

@@ -12,11 +12,13 @@ private:
static DICOMShader* instance; static DICOMShader* instance;
TSE::ITexture* DICOM; TSE::ITexture* DICOM;
TSE::ITexture* SmallDICOM; TSE::ITexture* SmallDICOM;
TSE::ITexture* LUT;
TSE::Vector3 size; TSE::Vector3 size;
TSE::Vector3 smallSize; TSE::Vector3 smallSize;
TSE::Vector3 scale; TSE::Vector3 scale;
float Threshold; float Threshold;
float stepSize; float stepSize;
float brickSize;
public: public:
static DICOMShader* Instance(); static DICOMShader* Instance();

View File

@@ -28,6 +28,7 @@ inline const char* fragDICOM = R"(
uniform sampler3D DICOMTexture; uniform sampler3D DICOMTexture;
uniform sampler3D SmallDICOMTexture; uniform sampler3D SmallDICOMTexture;
uniform sampler2D LUTTexture;
uniform vec3 CamPos; uniform vec3 CamPos;
uniform vec3 CamRight; uniform vec3 CamRight;
@@ -49,6 +50,8 @@ inline const char* fragDICOM = R"(
uniform float BrickScale; uniform float BrickScale;
const float EPSILON = 1e-6;
in DATA in DATA
{ {
vec2 uv_out; vec2 uv_out;
@@ -85,7 +88,7 @@ inline const char* fragDICOM = R"(
rayDir = CamForward; 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); int brickSize = int(brickScaleF);
@@ -120,16 +123,25 @@ inline const char* fragDICOM = R"(
int maxIters = brickSize * 3 + 8; int maxIters = brickSize * 3 + 8;
float hits = 0;
for (int i = 0; i < maxIters; ++i) for (int i = 0; i < maxIters; ++i)
{ {
if (t > blockTExit) if (t > blockTExit)
break; break;
float density = texelFetch(DICOMTexture, voxel, 0).r; float density = texelFetch(DICOMTexture, voxel, 0).r;
if (density >= Threshold) uint v = uint(density * 65535.0 + 0.5);
hits++; 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) 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) vec4 RenderVoxelDDA(vec3 rayOrigin, vec3 rayDir)
@@ -211,6 +223,8 @@ inline const char* fragDICOM = R"(
float increment = StepSize; float increment = StepSize;
vec4 colorAcum = vec4(0.0);
for (int i = 0; i < maxIters; ++i) for (int i = 0; i < maxIters; ++i)
{ {
if (t > tExit) if (t > tExit)
@@ -221,11 +235,12 @@ inline const char* fragDICOM = R"(
float cellTExit = min(min(tMax.x, tMax.y), tMax.z); float cellTExit = min(min(tMax.x, tMax.y), tMax.z);
cellTExit = min(cellTExit, tExit); cellTExit = min(cellTExit, tExit);
if (brickDensity >= Threshold) if (brickDensity >= EPSILON)
{ {
float hits = TraverseFineBlock(rayOrigin, rayDir, t, cellTExit, brick, brickScaleF, gridSize); colorAcum = TraverseFineBlock(rayOrigin, rayDir, t, cellTExit, brick, brickScaleF, gridSize, colorAcum);
count += hits * increment;
if(count >= 1) break; if (colorAcum.w > 0.98)
break;
} }
if (tMax.x <= tMax.y && tMax.x <= tMax.z) if (tMax.x <= tMax.y && tMax.x <= tMax.z)
@@ -250,9 +265,8 @@ inline const char* fragDICOM = R"(
tMax.z += tDelta.z; 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() void main()