diff --git a/Assets/Editor/VolumeLoader.cs b/Assets/Editor/VolumeLoader.cs new file mode 100644 index 00000000..e32dd000 --- /dev/null +++ b/Assets/Editor/VolumeLoader.cs @@ -0,0 +1,154 @@ +using UnityEditor; +using UnityEngine; +using System.IO; +using System.Collections.Generic; +using System; +using System.Linq; +using System.Threading.Tasks; + +namespace UnityVolumeRendering +{ + public class VolumeLoader + { + + public static void LoadNRRDDataset(VolumeRenderedObject renderedObject) + { + LoadNRRDDatasetAsync(renderedObject); + } + + public static void LoadNIFTIDataset(VolumeRenderedObject renderedObject) + { + LoadNIFTIDatasetAsync(renderedObject); + } + + public static void LoadImageFileDataset(VolumeRenderedObject renderedObject) + { + LoadImageFileDatasetAsync(renderedObject); + + } + + public static void LoadParDataset(VolumeRenderedObject renderedObject) + { + LoadParDatasetAsync(renderedObject); + } + + // TODO : Load Dicom Async + + public static async void LoadNRRDDatasetAsync(VolumeRenderedObject renderedObject) + { + if (!SimpleITKManager.IsSITKEnabled()) + { + if (EditorUtility.DisplayDialog("Missing SimpleITK", "You need to download SimpleITK to load NRRD datasets from the import settings menu.\n" + + "Do you want to open the import settings menu?", "Yes", "No")) + { + ImportSettingsEditorWindow.ShowWindow(); + } + } + + string file = EditorUtility.OpenFilePanel("Select a dataset to load (.nrrd)", "DataFiles", ""); + if (File.Exists(file)) + { + Debug.Log("Async dataset load. Hold on."); + using (ProgressHandler progressHandler = new ProgressHandler(new EditorProgressView(), "NRRD import")) + { + progressHandler.ReportProgress(0.0f, "Importing NRRD dataset"); + + IImageFileImporter importer = ImporterFactory.CreateImageFileImporter(ImageFileFormat.NRRD); + + VolumeDataset dataset = await importer.ImportAsync(file); + + renderedObject.SetSegmentationDataset(dataset); + + progressHandler.ReportProgress(0.8f, "Loading object"); + + } + } + else + { + Debug.LogError("File doesn't exist: " + file); + } + } + + public static async void LoadNIFTIDatasetAsync(VolumeRenderedObject renderedObject) + { + string file = EditorUtility.OpenFilePanel("Select a dataset to load (.nii)", "DataFiles", ""); + if (File.Exists(file)) + { + Debug.Log("Async dataset load. Hold on."); + using (ProgressHandler progressHandler = new ProgressHandler(new EditorProgressView(), "NIFTI import")) + { + progressHandler.ReportProgress(0.0f, "Importing NIfTI dataset"); + + IImageFileImporter importer = ImporterFactory.CreateImageFileImporter(ImageFileFormat.NIFTI); + + VolumeDataset dataset = await importer.ImportAsync(file); + + renderedObject.SetSegmentationDataset(dataset); + + progressHandler.ReportProgress(0.0f, "Loaded object"); + + + } + } + else + { + Debug.LogError("File doesn't exist: " + file); + + } + } + + public static async void LoadImageFileDatasetAsync(VolumeRenderedObject renderedObject) + { + string file = EditorUtility.OpenFilePanel("Select a dataset to load", "DataFiles", ""); + if (File.Exists(file)) + { + Debug.Log("Async dataset load. Hold on."); + using (ProgressHandler progressHandler = new ProgressHandler(new EditorProgressView(), "Image file import")) + { + progressHandler.ReportProgress(0.0f, "Importing image file dataset"); + + IImageFileImporter importer = ImporterFactory.CreateImageFileImporter(ImageFileFormat.Unknown); + + VolumeDataset dataset = await importer.ImportAsync(file); + + renderedObject.SetSegmentationDataset(dataset, progressHandler); + + progressHandler.ReportProgress(0.0f, "Loaded object"); + + } + } + else + { + Debug.LogError("File doesn't exist: " + file); + } + } + + public static async void LoadParDatasetAsync(VolumeRenderedObject renderedObject) + { + string file = EditorUtility.OpenFilePanel("Select a dataset to load", "DataFiles", ""); + if (File.Exists(file)) + { + Debug.Log("Async dataset load. Hold on."); + using (ProgressHandler progressHandler = new ProgressHandler(new EditorProgressView(), "AVSP import")) + { + progressHandler.ReportProgress(0.0f, "Importing VASP dataset"); + + IImageFileImporter importer = ImporterFactory.CreateImageFileImporter(ImageFileFormat.VASP); + + VolumeDataset dataset = await importer.ImportAsync(file); + + renderedObject.SetSegmentationDataset(dataset, progressHandler); + + progressHandler.ReportProgress(0.0f, "Loaded object"); + + } + } + else + { + Debug.LogError("File doesn't exist: " + file); + } + } + + // TODO : Load Sequence Async + } +} \ No newline at end of file diff --git a/Assets/Editor/VolumeRenderedObjectCustomInspector.cs b/Assets/Editor/VolumeRenderedObjectCustomInspector.cs index bd343c52..70a39175 100644 --- a/Assets/Editor/VolumeRenderedObjectCustomInspector.cs +++ b/Assets/Editor/VolumeRenderedObjectCustomInspector.cs @@ -74,6 +74,40 @@ public override void OnInspectorGUI() volrendObj.SetGradientVisibilityThreshold(newThreshold); } + + // Segmentations + EditorGUILayout.Space(); + var segmentationSettings = EditorGUILayout.Foldout(tfSettings, "Segmentations"); + if (segmentationSettings) + { + // File Format + ImageFileFormat format = (ImageFileFormat)EditorGUILayout.EnumPopup("File Format", ImageFileFormat.NIFTI); + + // Import Button + if (GUILayout.Button("Import")) + { + switch (format) + { + case ImageFileFormat.VASP: + VolumeLoader.LoadParDataset(volrendObj); + break; + case ImageFileFormat.NRRD: + VolumeLoader.LoadNRRDDataset(volrendObj); + break; + case ImageFileFormat.NIFTI: + VolumeLoader.LoadNIFTIDataset(volrendObj); + break; + case ImageFileFormat.Unknown: + Debug.Log("The file format is unknown."); + break; + default: + Debug.Log("Invalid file format."); + break; + } + + } + } + // Transfer function settings EditorGUILayout.Space(); tfSettings = EditorGUILayout.Foldout(tfSettings, "Transfer function"); diff --git a/Assets/Scripts/VolumeObject/VolumeRenderedObject.cs b/Assets/Scripts/VolumeObject/VolumeRenderedObject.cs index 396ec67b..eef17462 100644 --- a/Assets/Scripts/VolumeObject/VolumeRenderedObject.cs +++ b/Assets/Scripts/VolumeObject/VolumeRenderedObject.cs @@ -16,6 +16,9 @@ public class VolumeRenderedObject : MonoBehaviour [SerializeField, HideInInspector] public VolumeDataset dataset; + [SerializeField, HideInInspector] + public VolumeDataset segmentationDataset; + [SerializeField, HideInInspector] public MeshRenderer meshRenderer; @@ -96,6 +99,15 @@ public async Task SetRenderModeAsync(RenderMode mode, IProgressHandler progressH } await UpdateMaterialPropertiesAsync(progressHandler); } + + public void SetSegmentationDataset(VolumeDataset dataset, IProgressHandler progressHandler = null) + { + this.segmentationDataset = dataset; + + UpdateMaterialProperties(progressHandler); + + } + public void SetTransferFunctionMode(TFRenderMode mode) { @@ -307,14 +319,20 @@ public void UpdateMaterialProperties(IProgressHandler progressHandler = null) private async Task UpdateMaterialPropertiesAsync(IProgressHandler progressHandler = null) { await updateMatLock.WaitAsync(); - + 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); + Texture3D segmentationTexture = await segmentationDataset.GetDataTextureAsync(progressHandler); + + meshRenderer.sharedMaterial.SetTexture("_DataTex", dataTexture); + meshRenderer.sharedMaterial.SetTexture("_SegmentationTex", segmentationTexture); meshRenderer.sharedMaterial.SetTexture("_GradientTex", gradientTexture); + UpdateMatInternal(); } finally @@ -329,7 +347,8 @@ private void UpdateMatInternal() { meshRenderer.sharedMaterial.SetTexture("_DataTex", dataset.GetDataTexture()); } - + meshRenderer.sharedMaterial.SetTexture("_SegmentationTex", segmentationDataset.GetDataTexture()); + if (meshRenderer.sharedMaterial.GetTexture("_NoiseTex") == null) { const int noiseDimX = 512; diff --git a/Assets/Shaders/DirectVolumeRenderingShader.shader b/Assets/Shaders/DirectVolumeRenderingShader.shader index 37413038..1199945a 100644 --- a/Assets/Shaders/DirectVolumeRenderingShader.shader +++ b/Assets/Shaders/DirectVolumeRenderingShader.shader @@ -3,6 +3,7 @@ Properties { _DataTex ("Data Texture (Generated)", 3D) = "" {} + _SegmentationTex ("Segmentation Texture (Generated)", 3D) = "" {} _GradientTex("Gradient Texture (Generated)", 3D) = "" {} _NoiseTex("Noise Texture (Generated)", 2D) = "white" {} _TFTex("Transfer Function Texture (Generated)", 2D) = "" {} @@ -72,6 +73,7 @@ }; sampler3D _DataTex; + sampler3D _SegmentationTex; sampler3D _GradientTex; sampler2D _NoiseTex; sampler2D _TFTex; @@ -207,6 +209,12 @@ return tex3Dlod(_DataTex, float4(pos.x, pos.y, pos.z, 0.0f)); } + // Get segmentation label + float getLabel(float3 pos) + { + return tex3Dlod(_SegmentationTex, float4(pos.x, pos.y, pos.z, 0.0f)); + } + // Gets the gradient at the specified position float3 getGradient(float3 pos) { @@ -312,13 +320,20 @@ // Get the dansity/sample value of the current position const float density = getDensity(currPos); + const float segmentationLabel = getLabel(currPos); // Apply visibility window if (density < _MinVal || density > _MaxVal) continue; // Apply 1D transfer function #if !TF2D_ON + float4 src = getTF1DColour(density); + if (segmentationLabel == 1 ) + { + src = float4(1.0, 0.0, 0.0, 1.0); + } + if (src.a == 0.0) continue; #endif diff --git a/CREDITS.md b/CREDITS.md index 56a11822..bfa6f433 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -1,22 +1,24 @@ Contributors: - [Matias Lavik](https://github.com/mlavik1) -Original project. + Original project. - [jasonks2](https://github.com/jasonks2): -Implemented support for PARCHG (.vasp) datasets. + Implemented support for PARCHG (.vasp) datasets. - [denistribouillois](https://github.com/denistribouillois) -Slicing plane improvements. Histogram GPU calculation. + Slicing plane improvements. Histogram GPU calculation. - [Michael Ovens](https://github.com/MichaelOvens) -Image sequence import. Other contributions. + Image sequence import. Other contributions. - [Chiara Di Vece](https://github.com/chiaradivece) -GUI for modifying slicing plane positiomn/orientation. + GUI for modifying slicing plane positiomn/orientation. - [btsai-dev](https://github.com/btsai-dev) -Memory leak fix + Memory leak fix - [Vahid](https://github.com/vahpy) -Texture downscaling, optimisation. + Texture downscaling, optimisation. - [SitronX](https://github.com/SitronX) -Async loading + Async loading - [Riccardo Lops](https://github.com/riccardolops) -Modified shader to handle stereo rendering. + Modified shader to handle stereo rendering. +- [Juan Pablo Montoya](https://github.com/JuanPabloMontoya271) +- Overlay Segmentations Feel free to add yourself to this list when contributing to this project.