diff --git a/Assets/Editor/DatasetImporterEditorWIndow.cs b/Assets/Editor/DatasetImporterEditorWIndow.cs
index efbc363..eaa6bdf 100644
--- a/Assets/Editor/DatasetImporterEditorWIndow.cs
+++ b/Assets/Editor/DatasetImporterEditorWIndow.cs
@@ -5,6 +5,9 @@ using System;
namespace UnityVolumeRendering
{
+ ///
+ /// Editor window for importing datasets.
+ ///
public class DatasetImporterEditorWindow : EditorWindow
{
private enum DatasetType
@@ -17,9 +20,9 @@ namespace UnityVolumeRendering
private string fileToImport;
private DatasetType datasetType;
- private int dimX; // TODO: set good default value
- private int dimY; // TODO: set good default value
- private int dimZ; // TODO: set good default value
+ private int dimX;
+ private int dimY;
+ private int dimZ;
private int bytesToSkip = 0;
private DataContentFormat dataFormat = DataContentFormat.Int16;
private Endianness endianness = Endianness.LittleEndian;
diff --git a/Assets/Editor/SliceRenderingEditorWindow.cs b/Assets/Editor/SliceRenderingEditorWindow.cs
index eced0b0..ec7f233 100644
--- a/Assets/Editor/SliceRenderingEditorWindow.cs
+++ b/Assets/Editor/SliceRenderingEditorWindow.cs
@@ -76,12 +76,12 @@ namespace UnityVolumeRendering
// Show buttons for changing the active plane
if (spawnedPlanes.Length > 0)
{
- if (GUI.Button(new Rect(0.0f, bgRect.y + bgRect.height + 20.0f, 100.0f, 30.0f), "previous\nplane"))
+ if (GUI.Button(new Rect(0.0f, bgRect.y + bgRect.height + 20.0f, 70.0f, 30.0f), "previous\nplane"))
{
selectedPlaneIndex = (selectedPlaneIndex - 1) % spawnedPlanes.Length;
Selection.activeGameObject = spawnedPlanes[selectedPlaneIndex].gameObject;
}
- if (GUI.Button(new Rect(120.0f, bgRect.y + bgRect.height + 20.0f, 100.0f, 30.0f), "next\nplane"))
+ if (GUI.Button(new Rect(90.0f, bgRect.y + bgRect.height + 20.0f, 70.0f, 30.0f), "next\nplane"))
{
selectedPlaneIndex = (selectedPlaneIndex + 1) % spawnedPlanes.Length;
Selection.activeGameObject = spawnedPlanes[selectedPlaneIndex].gameObject;
@@ -89,7 +89,7 @@ namespace UnityVolumeRendering
}
// Show button for adding new plane
- if (GUI.Button(new Rect(240.0f, bgRect.y + bgRect.height + 20.0f, 100.0f, 30.0f), "add\nplane"))
+ if (GUI.Button(new Rect(180.0f, bgRect.y + bgRect.height + 20.0f, 70.0f, 30.0f), "add\nplane"))
{
VolumeRenderedObject volRend = FindObjectOfType();
if (volRend != null)
@@ -99,6 +99,13 @@ namespace UnityVolumeRendering
}
}
+ // Show button for removing
+ if (spawnedPlanes.Length > 0 && GUI.Button(new Rect(270.0f, bgRect.y + bgRect.height + 20.0f, 70.0f, 30.0f), "remove\nplane"))
+ {
+ SlicingPlane planeToRemove = spawnedPlanes[selectedPlaneIndex];
+ GameObject.DestroyImmediate(planeToRemove.gameObject);
+ }
+
// Show hint
if (spawnedPlanes.Length > 0)
GUI.Label(new Rect(0.0f, bgRect.y + bgRect.height + 60.0f, 450.0f, 30.0f), "Move plane by left clicking in the above view and dragging the mouse,\n or simply move it in the object hierarchy.");
diff --git a/Assets/Scripts/Importing/DICOMImporter.cs b/Assets/Scripts/Importing/DICOMImporter.cs
index 48a6888..4d58745 100644
--- a/Assets/Scripts/Importing/DICOMImporter.cs
+++ b/Assets/Scripts/Importing/DICOMImporter.cs
@@ -11,6 +11,11 @@ using System.Linq;
namespace UnityVolumeRendering
{
+ ///
+ /// DICOM importer.
+ /// Reads a 3D DICOM dataset from a folder.
+ /// The folder needs to contain several .dcm/.dicom files, where each file is a slice of the same dataset.
+ ///
public class DICOMImporter : DatasetImporterBase
{
private class DICOMSliceFile
@@ -45,50 +50,17 @@ namespace UnityVolumeRendering
return null;
}
+ // Read all files
IEnumerable fileCandidates = Directory.EnumerateFiles(diroctoryPath, "*.*", recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly)
.Where(p => p.EndsWith(".dcm") || p.EndsWith(".dicom") || p.EndsWith(".dicm"));
List files = new List();
foreach (string filePath in fileCandidates)
{
- AcrNemaFile file = LoadFile(filePath);
- if(file != null && file.HasPixelData)
- {
- DICOMSliceFile slice = new DICOMSliceFile();
- slice.file = file;
- // Read location
- Tag locTag = new Tag("(0020,1041)");
- if(file.DataSet.Contains(locTag))
- {
- DataElement elemLoc = file.DataSet[locTag];
- slice.location = (float)Convert.ToDouble(elemLoc.Value[0]);
- }
- else
- {
- Debug.LogError($"Missing location tag in file: {filePath}.\n The file will not be imported");
- continue;
- }
- // Read intercept
- Tag interceptTag = new Tag("(0028,1052)");
- if (file.DataSet.Contains(interceptTag))
- {
- DataElement elemIntercept = file.DataSet[interceptTag];
- slice.intercept = (float)Convert.ToDouble(elemIntercept.Value[0]);
- }
- else
- Debug.LogWarning($"The file {filePath} is missing the intercept element. As a result, the default transfer function might not look good.");
- // Read slope
- Tag slopeTag = new Tag("(0028,1053)");
- if (file.DataSet.Contains(slopeTag))
- {
- DataElement elemSlope = file.DataSet[slopeTag];
- slice.slope = (float)Convert.ToDouble(elemSlope.Value[0]);
- }
- else
- Debug.LogWarning($"The file {filePath} is missing the intercept element. As a result, the default transfer function might not look good.");
-
- files.Add(slice);
- }
+ DICOMSliceFile sliceFile = ReadDICOMFile(filePath);
+ if(sliceFile != null)
+ files.Add(sliceFile);
}
+ // Sort files by slice location
files.Sort((DICOMSliceFile a, DICOMSliceFile b) => { return a.location.CompareTo(b.location); });
Debug.Log($"Imported {files.Count} datasets");
@@ -103,6 +75,7 @@ namespace UnityVolumeRendering
float maxLoc = (float)files[files.Count - 1].location;
float locRange = maxLoc - minLoc;
+ // Create dataset
VolumeDataset dataset = new VolumeDataset();
dataset.dimX = files[0].file.PixelData.Columns;
dataset.dimY = files[0].file.PixelData.Rows;
@@ -137,6 +110,50 @@ namespace UnityVolumeRendering
return dataset;
}
+ private DICOMSliceFile ReadDICOMFile(string filePath)
+ {
+ AcrNemaFile file = LoadFile(filePath);
+
+ if (file != null && file.HasPixelData)
+ {
+ DICOMSliceFile slice = new DICOMSliceFile();
+ slice.file = file;
+ // Read location
+ Tag locTag = new Tag("(0020,1041)");
+ if (file.DataSet.Contains(locTag))
+ {
+ DataElement elemLoc = file.DataSet[locTag];
+ slice.location = (float)Convert.ToDouble(elemLoc.Value[0]);
+ }
+ else
+ {
+ Debug.LogError($"Missing location tag in file: {filePath}.\n The file will not be imported");
+ return null;
+ }
+ // Read intercept
+ Tag interceptTag = new Tag("(0028,1052)");
+ if (file.DataSet.Contains(interceptTag))
+ {
+ DataElement elemIntercept = file.DataSet[interceptTag];
+ slice.intercept = (float)Convert.ToDouble(elemIntercept.Value[0]);
+ }
+ else
+ Debug.LogWarning($"The file {filePath} is missing the intercept element. As a result, the default transfer function might not look good.");
+ // Read slope
+ Tag slopeTag = new Tag("(0028,1053)");
+ if (file.DataSet.Contains(slopeTag))
+ {
+ DataElement elemSlope = file.DataSet[slopeTag];
+ slice.slope = (float)Convert.ToDouble(elemSlope.Value[0]);
+ }
+ else
+ Debug.LogWarning($"The file {filePath} is missing the intercept element. As a result, the default transfer function might not look good.");
+
+ return slice;
+ }
+ return null;
+ }
+
private AcrNemaFile LoadFile(string filePath)
{
AcrNemaFile file = null;
diff --git a/Assets/Scripts/Importing/DatasetImporterBase.cs b/Assets/Scripts/Importing/DatasetImporterBase.cs
index bb08d66..1c28e71 100644
--- a/Assets/Scripts/Importing/DatasetImporterBase.cs
+++ b/Assets/Scripts/Importing/DatasetImporterBase.cs
@@ -1,5 +1,9 @@
namespace UnityVolumeRendering
{
+ ///
+ /// Base class for all dataset imports.
+ /// If you want to add support for a new format, create a sublcass of this.
+ ///
public abstract class DatasetImporterBase
{
public abstract VolumeDataset Import();
diff --git a/Assets/Scripts/Importing/DatasetIniReader.cs b/Assets/Scripts/Importing/DatasetIniReader.cs
index 6690672..c74c630 100644
--- a/Assets/Scripts/Importing/DatasetIniReader.cs
+++ b/Assets/Scripts/Importing/DatasetIniReader.cs
@@ -13,6 +13,17 @@ namespace UnityVolumeRendering
public Endianness endianness = Endianness.LittleEndian;
}
+ ///
+ /// .ini-file reader for raw datasets.
+ /// .ini files contains information about how to import a raw dataset file.
+ /// Example file:
+ /// dimx:256
+ /// dimy:256
+ /// dimz:68
+ /// skip:28
+ /// format:uint8
+ /// "skip" defines how many bytes to skip (file header) - it should be 0 if the file has no header, which is often the case.
+ ///
public class DatasetIniReader
{
public static DatasetIniData ParseIniFile(string filePath)
diff --git a/Assets/Scripts/VolumeObject/CrossSectionPlane.cs b/Assets/Scripts/VolumeObject/CrossSectionPlane.cs
new file mode 100644
index 0000000..ed43231
--- /dev/null
+++ b/Assets/Scripts/VolumeObject/CrossSectionPlane.cs
@@ -0,0 +1,35 @@
+using UnityEngine;
+
+namespace UnityVolumeRendering
+{
+ ///
+ /// Cross section plane.
+ /// Used for cutting a model (cross section view).
+ ///
+ [ExecuteInEditMode]
+ public class CrossSectionPlane : MonoBehaviour
+ {
+ ///
+ /// Volume dataset to cross section.
+ ///
+ public VolumeRenderedObject targetObject;
+
+ private void OnDisable()
+ {
+ if (targetObject != null)
+ targetObject.meshRenderer.sharedMaterial.DisableKeyword("SLICEPLANE_ON");
+ }
+
+ private void Update()
+ {
+ if (targetObject == null)
+ return;
+
+ Material mat = targetObject.meshRenderer.sharedMaterial;
+
+ mat.EnableKeyword("SLICEPLANE_ON");
+ mat.SetVector("_PlanePos", targetObject.transform.position - transform.position);
+ mat.SetVector("_PlaneNormal", transform.forward);
+ }
+ }
+}
diff --git a/Assets/Scripts/VolumeObject/SlicingPlaneAnyDirection.cs.meta b/Assets/Scripts/VolumeObject/CrossSectionPlane.cs.meta
similarity index 100%
rename from Assets/Scripts/VolumeObject/SlicingPlaneAnyDirection.cs.meta
rename to Assets/Scripts/VolumeObject/CrossSectionPlane.cs.meta
diff --git a/Assets/Scripts/VolumeObject/SlicingPlaneAnyDirection.cs b/Assets/Scripts/VolumeObject/SlicingPlaneAnyDirection.cs
deleted file mode 100644
index 49ac541..0000000
--- a/Assets/Scripts/VolumeObject/SlicingPlaneAnyDirection.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-using UnityEngine;
-
-namespace UnityVolumeRendering
-{
- [ExecuteInEditMode]
- public class SlicingPlaneAnyDirection : MonoBehaviour
- {
- public Material mat;
- public Transform volumeTransform;
-
- private void OnDisable()
- {
- if (mat != null)
- mat.DisableKeyword("SLICEPLANE_ON");
- }
-
- private void Update()
- {
- if (mat == null || volumeTransform == null)
- return;
-
- mat.EnableKeyword("SLICEPLANE_ON");
- mat.SetVector("_PlanePos", volumeTransform.position - transform.position);
- mat.SetVector("_PlaneNormal", transform.forward);
- }
- }
-}
diff --git a/Assets/Scripts/VolumeObject/VolumeObjectFactory.cs b/Assets/Scripts/VolumeObject/VolumeObjectFactory.cs
index cd5880a..961b8fb 100644
--- a/Assets/Scripts/VolumeObject/VolumeObjectFactory.cs
+++ b/Assets/Scripts/VolumeObject/VolumeObjectFactory.cs
@@ -49,9 +49,8 @@ namespace UnityVolumeRendering
{
GameObject quad = GameObject.CreatePrimitive(PrimitiveType.Quad);
quad.transform.rotation = Quaternion.Euler(270.0f, 0.0f, 0.0f);
- SlicingPlaneAnyDirection csplane = quad.gameObject.AddComponent();
- csplane.mat = volobj.meshRenderer.sharedMaterial;
- csplane.volumeTransform = volobj.transform;
+ CrossSectionPlane csplane = quad.gameObject.AddComponent();
+ csplane.targetObject = volobj;
quad.transform.position = volobj.transform.position;
#if UNITY_EDITOR