Skip to content

Commit

Permalink
Support for secondary volume (PET/CT or segmentation)
Browse files Browse the repository at this point in the history
  • Loading branch information
mlavik1 committed Aug 24, 2024
1 parent 59606fb commit 45e2a0d
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 7 deletions.
43 changes: 43 additions & 0 deletions Assets/Editor/VolumeRendererEditorFunctions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ private static void ShowDICOMImporter()
DicomImportAsync(true);
}

[MenuItem("Volume Rendering/Load dataset/Load PET-CT DICOM")]
private static void ShowPETCTDICOMImporter()
{
PETCTDicomImportAsync();
}

[MenuItem("Assets/Volume Rendering/Import dataset/Import DICOM")]
private static void ImportDICOMAsset()
{
Expand Down Expand Up @@ -79,6 +85,43 @@ private static async void DicomImportAsync(bool spawnInScene)
}
}

private static async void PETCTDicomImportAsync()
{
string dirCT = EditorUtility.OpenFolderPanel("Select a CT DICOM folder to load", "", "");
string dirPET = EditorUtility.OpenFolderPanel("Select a PET DICOM folder to load", "", "");
if (Directory.Exists(dirCT) && Directory.Exists(dirPET))
{
Debug.Log("Async dataset load. Hold on.");
using (ProgressHandler progressHandler = new ProgressHandler(new EditorProgressView()))
{
progressHandler.StartStage(0.35f, "Importing CT dataset");
Task<VolumeDataset[]> importTaskCT = DicomImportDirectoryAsync(dirCT, progressHandler);
await importTaskCT;
progressHandler.EndStage();
Debug.Assert(importTaskCT.Result.Length > 0);
progressHandler.StartStage(0.35f, "Importing PET dataset");
Task<VolumeDataset[]> importTaskPET = DicomImportDirectoryAsync(dirPET, progressHandler);
await importTaskPET;
progressHandler.EndStage();
Debug.Assert(importTaskPET.Result.Length > 0);
progressHandler.StartStage(0.3f, "Spawning dataset");
VolumeDataset datasetCT = importTaskCT.Result[0];
VolumeDataset datasetPET = importTaskPET.Result[0];
VolumeRenderedObject objCT = await VolumeObjectFactory.CreateObjectAsync(datasetCT);
VolumeRenderedObject objPET = await VolumeObjectFactory.CreateObjectAsync(datasetPET);
objPET.transferFunction.colourControlPoints = new List<TFColourControlPoint>() { new TFColourControlPoint(0.0f, Color.red), new TFColourControlPoint(1.0f, Color.red) };
objPET.transferFunction.GenerateTexture();
progressHandler.EndStage();
objCT.SetSecondaryVolume(objPET);
objPET.gameObject.SetActive(false);
}
}
else
{
Debug.LogError("Directory doesn't exist");
}
}

private static async Task<VolumeDataset[]> DicomImportDirectoryAsync(string dir, ProgressHandler progressHandler)
{
Debug.Log("Async dataset load. Hold on.");
Expand Down
39 changes: 32 additions & 7 deletions Assets/Scripts/VolumeObject/VolumeRenderedObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ public class VolumeRenderedObject : MonoBehaviour
[SerializeField, HideInInspector]
private LightSource lightSource;

[SerializeField, HideInInspector]
private VolumeRenderedObject secondaryVolume;

// Minimum and maximum gradient threshold for lighting contribution. Values below min will be unlit, and between min and max will be partly shaded.
[SerializeField, HideInInspector]
private Vector2 gradientLightingThreshold = new Vector2(0.02f, 0.15f);
Expand Down Expand Up @@ -82,6 +85,12 @@ public SlicingPlane CreateSlicingPlane()
return slicingPlaneComp;
}

public void SetSecondaryVolume(VolumeRenderedObject volumeObject)
{
this.secondaryVolume = volumeObject;
UpdateMaterialProperties();
}

public void SetRenderMode(RenderMode mode)
{
Task task = SetRenderModeAsync(mode);
Expand Down Expand Up @@ -317,23 +326,39 @@ private async Task UpdateMaterialPropertiesAsync(IProgressHandler progressHandle
try
{
bool useGradientTexture = tfRenderMode == TFRenderMode.TF2D || renderMode == RenderMode.IsosurfaceRendering || lightingEnabled;
Texture3D gradientTexture = useGradientTexture ? await dataset.GetGradientTextureAsync(progressHandler) : null;
Texture3D dataTexture = await dataset.GetDataTextureAsync(progressHandler);
meshRenderer.sharedMaterial.SetTexture("_DataTex", dataTexture);
meshRenderer.sharedMaterial.SetTexture("_GradientTex", gradientTexture);
UpdateMatInternal();
Texture3D gradientTexture = useGradientTexture ? await dataset.GetGradientTextureAsync(progressHandler) : null;
Texture3D secondaryDataTexture = await secondaryVolume?.dataset?.GetDataTextureAsync(progressHandler);
UpdateMatInternal(dataTexture, gradientTexture, secondaryDataTexture);
}
finally
{
updateMatLock.Release();
}
}

private void UpdateMatInternal()
private void UpdateMatInternal(Texture3D dataTexture, Texture3D gradientTexture, Texture3D secondaryDataTexture)
{
if (meshRenderer.sharedMaterial.GetTexture("_DataTex") == null)
if (dataTexture != null)
{
meshRenderer.sharedMaterial.SetTexture("_DataTex", dataset.GetDataTexture());
meshRenderer.sharedMaterial.SetTexture("_DataTex", dataTexture);
}

if (gradientTexture != null)
{
meshRenderer.sharedMaterial.SetTexture("_GradientTex", gradientTexture);
}

if (secondaryDataTexture != null)
{
Texture2D secondaryTF = secondaryVolume.transferFunction.GetTexture();
meshRenderer.sharedMaterial.SetTexture("_SecondaryDataTex", secondaryDataTexture);
meshRenderer.sharedMaterial.SetTexture("_SecondaryTFTex", secondaryTF);
meshRenderer.sharedMaterial.EnableKeyword("SECONDARY_VOLUME_ON");
}
else
{
meshRenderer.sharedMaterial.DisableKeyword("SECONDARY_VOLUME_ON");
}

if (meshRenderer.sharedMaterial.GetTexture("_NoiseTex") == null)
Expand Down
29 changes: 29 additions & 0 deletions Assets/Shaders/DirectVolumeRenderingShader.shader
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
_MinGradient("Gradient visibility threshold", Range(0.0, 1.0)) = 0.0
_LightingGradientThresholdStart("Gradient threshold for lighting (end)", Range(0.0, 1.0)) = 0.0
_LightingGradientThresholdEnd("Gradient threshold for lighting (start)", Range(0.0, 1.0)) = 0.0
_SecondaryDataTex ("Secondary Data Texture (Generated)", 3D) = "" {}
_SecondaryTFTex("Transfer Function Texture for secondary volume", 2D) = "" {}
[HideInInspector] _ShadowVolumeTextureSize("Shadow volume dimensions", Vector) = (1, 1, 1)
[HideInInspector] _TextureSize("Dataset dimensions", Vector) = (1, 1, 1)
}
Expand All @@ -37,6 +39,7 @@
#pragma multi_compile __ RAY_TERMINATE_ON
#pragma multi_compile __ USE_MAIN_LIGHT
#pragma multi_compile __ CUBIC_INTERPOLATION_ON
#pragma multi_compile __ SECONDARY_VOLUME_ON
#pragma vertex vert
#pragma fragment frag

Expand Down Expand Up @@ -76,6 +79,8 @@
sampler2D _NoiseTex;
sampler2D _TFTex;
sampler3D _ShadowVolume;
sampler3D _SecondaryDataTex;
sampler2D _SecondaryTFTex;

float _MinVal;
float _MaxVal;
Expand Down Expand Up @@ -191,6 +196,12 @@
return tex2Dlod(_TFTex, float4(density, gradientMagnitude, 0.0f, 0.0f));
}

// Gets the colour from a secondary 1D Transfer Function (x = density)
float4 getSecondaryTF1DColour(float density)
{
return tex2Dlod(_SecondaryTFTex, float4(density, 0.0f, 0.0f, 0.0f));
}

// Gets the density at the specified position
float getDensity(float3 pos)
{
Expand All @@ -201,6 +212,16 @@
#endif
}

// Gets the density of the secondary volume at the specified position
float getSecondaryDensity(float3 pos)
{
#if CUBIC_INTERPOLATION_ON
return interpolateTricubicFast(_SecondaryDataTex, float3(pos.x, pos.y, pos.z), _TextureSize);
#else
return tex3Dlod(_SecondaryDataTex, float4(pos.x, pos.y, pos.z, 0.0f));
#endif
}

// Gets the density at the specified position, without tricubic interpolation
float getDensityNoTricubic(float3 pos)
{
Expand Down Expand Up @@ -323,6 +344,14 @@
continue;
#endif

#if SECONDARY_VOLUME_ON
const float density2 = getSecondaryDensity(currPos);
float4 src2 = getSecondaryTF1DColour(density2);
//src.rgb = src.rgb * (1.0 - src2.a) + src2.rgb * src2.a;
src = src2.a > 0.0 ? src2 : src;
//src.a = src2.a > 0.0 ? src.a : 0.0;
#endif

// Calculate gradient (needed for lighting and 2D transfer functions)
#if defined(TF2D_ON) || defined(LIGHTING_ON)
float3 gradient = getGradient(currPos);
Expand Down

0 comments on commit 45e2a0d

Please sign in to comment.