Added "remove plane" button to slicing renderer window.
Renamed SlicingPlaneAnyDirection -> CrossSectionPlane. Added more comments.
This commit is contained in:
Родитель
d3a6049884
Коммит
c6dcc731a7
|
@ -5,6 +5,9 @@ using System;
|
|||
|
||||
namespace UnityVolumeRendering
|
||||
{
|
||||
/// <summary>
|
||||
/// Editor window for importing datasets.
|
||||
/// </summary>
|
||||
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;
|
||||
|
|
|
@ -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<VolumeRenderedObject>();
|
||||
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.");
|
||||
|
|
|
@ -11,6 +11,11 @@ using System.Linq;
|
|||
|
||||
namespace UnityVolumeRendering
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public class DICOMImporter : DatasetImporterBase
|
||||
{
|
||||
private class DICOMSliceFile
|
||||
|
@ -45,50 +50,17 @@ namespace UnityVolumeRendering
|
|||
return null;
|
||||
}
|
||||
|
||||
// Read all files
|
||||
IEnumerable<string> fileCandidates = Directory.EnumerateFiles(diroctoryPath, "*.*", recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly)
|
||||
.Where(p => p.EndsWith(".dcm") || p.EndsWith(".dicom") || p.EndsWith(".dicm"));
|
||||
List<DICOMSliceFile> files = new List<DICOMSliceFile>();
|
||||
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;
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
namespace UnityVolumeRendering
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for all dataset imports.
|
||||
/// If you want to add support for a new format, create a sublcass of this.
|
||||
/// </summary>
|
||||
public abstract class DatasetImporterBase
|
||||
{
|
||||
public abstract VolumeDataset Import();
|
||||
|
|
|
@ -13,6 +13,17 @@ namespace UnityVolumeRendering
|
|||
public Endianness endianness = Endianness.LittleEndian;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// .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.
|
||||
/// </summary>
|
||||
public class DatasetIniReader
|
||||
{
|
||||
public static DatasetIniData ParseIniFile(string filePath)
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
using UnityEngine;
|
||||
|
||||
namespace UnityVolumeRendering
|
||||
{
|
||||
/// <summary>
|
||||
/// Cross section plane.
|
||||
/// Used for cutting a model (cross section view).
|
||||
/// </summary>
|
||||
[ExecuteInEditMode]
|
||||
public class CrossSectionPlane : MonoBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// Volume dataset to cross section.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<SlicingPlaneAnyDirection>();
|
||||
csplane.mat = volobj.meshRenderer.sharedMaterial;
|
||||
csplane.volumeTransform = volobj.transform;
|
||||
CrossSectionPlane csplane = quad.gameObject.AddComponent<CrossSectionPlane>();
|
||||
csplane.targetObject = volobj;
|
||||
quad.transform.position = volobj.transform.position;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
|
Загрузка…
Ссылка в новой задаче