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 "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<float>("threshold", 0.4f);
canvasMat->SetValue<float>("stepSize", 0.01f);
canvasMat->SetValue<float>("brickSize", smallDicomDevider);
canvasMat->SetValue<Vector3>("texSize", textureSize);
canvasMat->SetValue<Vector3>("smallTexSize", smallTextureSize);
Vector3 objectScale = Vector3(1,1,1);
canvasMat->SetValue<Vector3>("ObjectScale", objectScale);
canvasMat->SetValue<ITexture*>("DICOM", dicom);
canvasMat->SetValue<ITexture*>("DICOMsmall", dicomSmall);
canvasMat->SetValue<ITexture*>("LUT", lutTexture);
// canvasMat->SetValue<uint>("heightTextureID", rt->GetTextureId(1));
// canvasMat->SetValue<uint>("depthTextureID", rt->GetTextureId(2));
// canvasMat->SetValue<uint>("colorTexture2ID", rtProps->GetTextureId(0));

View File

@@ -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<float>("threshold");
stepSize = r->GetMaterial()->GetValue<float>("stepSize");
brickSize = r->GetMaterial()->GetValue<float>("brickSize");
size = r->GetMaterial()->GetValue<Vector3>("texSize");
smallSize = r->GetMaterial()->GetValue<Vector3>("smallTexSize");
scale = r->GetMaterial()->GetValue<Vector3>("ObjectScale");
DICOM = r->GetMaterial()->GetValue<ITexture*>("DICOM");
LUT = r->GetMaterial()->GetValue<ITexture*>("LUT");
SmallDICOM = r->GetMaterial()->GetValue<ITexture*>("DICOMsmall");
const Vector3* verts = r->GetVertices();

View File

@@ -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();

View File

@@ -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()