From 6dfab009ba5c79766b4e259896402519828d4d40 Mon Sep 17 00:00:00 2001
From: Vahid <92767644+vahpy@users.noreply.github.com>
Date: Sat, 23 Oct 2021 22:14:47 +1100
Subject: [PATCH 1/3] New implementation for texture downscaling
Less code and less memory usage
---
Assets/Scripts/VolumeData/VolumeDataset.cs | 181 ++++-----------------
1 file changed, 29 insertions(+), 152 deletions(-)
diff --git a/Assets/Scripts/VolumeData/VolumeDataset.cs b/Assets/Scripts/VolumeData/VolumeDataset.cs
index 81ad180..b6fa9ac 100644
--- a/Assets/Scripts/VolumeData/VolumeDataset.cs
+++ b/Assets/Scripts/VolumeData/VolumeDataset.cs
@@ -13,12 +13,8 @@ namespace UnityVolumeRendering
[SerializeField]
public int[] data = null;
- // Flattened 3D array of downscaled data sample values.
[SerializeField]
- public float[] downScaledData = null;
-
- [SerializeField]
- public int dimX, dimY, dimZ, halfDimX, halfDimY, halfDimZ;
+ public int dimX, dimY, dimZ;
[SerializeField]
public float scaleX = 0.0f, scaleY = 0.0f, scaleZ = 0.0f;
@@ -30,23 +26,12 @@ namespace UnityVolumeRendering
private int maxDataValue = int.MinValue;
private Texture3D dataTexture = null;
private Texture3D gradientTexture = null;
- private bool downScaled = false;
public Texture3D GetDataTexture()
- {
- return GetDataTexture(false);
- }
-
- public Texture3D GetDataTexture(bool forceDownscaling)
{
if (dataTexture == null)
{
- if (forceDownscaling /* || some conditions */)
- {
- dataTexture = CreateDownScaledTextureInternal();
- downScaled = true;
- }
- else dataTexture = CreateTextureInternal();
+ dataTexture = CreateTextureInternal();
}
return dataTexture;
}
@@ -55,8 +40,7 @@ namespace UnityVolumeRendering
{
if (gradientTexture == null)
{
- if (downScaled) gradientTexture = CreateDownScaledGradientTextureInternal();
- else gradientTexture = CreateGradientTextureInternal();
+ gradientTexture = CreateGradientTextureInternal();
}
return gradientTexture;
}
@@ -82,32 +66,40 @@ namespace UnityVolumeRendering
{
int MAX_DIM = 2048; // 3D texture max size. See: https://docs.unity3d.com/Manual/class-Texture3D.html
- if (Mathf.Max(dimX, dimY, dimZ) > MAX_DIM)
+ while (Mathf.Max(dimX, dimY, dimZ) > MAX_DIM)
{
- Debug.LogWarning("Dimension exceeds limits. Cropping dataset. This might result in an incomplete dataset.");
+ Debug.LogWarning("Dimension exceeds limits (maximum: "+MAX_DIM+"). Dataset is downscaled by 2 on each axis!");
+ DownScaleData();
+ }
+ }
- int newDimX = Mathf.Min(dimX, MAX_DIM);
- int newDimY = Mathf.Min(dimY, MAX_DIM);
- int newDimZ = Mathf.Min(dimZ, MAX_DIM);
- int[] newData = new int[dimX * dimY * dimZ];
+ ///
+ /// Downscales the data by averaging 8 voxels per each new voxel,
+ /// and replaces downscaled data with the original data
+ ///
+ public void DownScaleData()
+ {
+ int halfDimX = dimX / 2 + dimX % 2;
+ int halfDimY = dimY / 2 + dimY % 2;
+ int halfDimZ = dimZ / 2 + dimZ % 2;
+ int[] downScaledData = new int[halfDimX * halfDimY * halfDimZ];
- for (int z = 0; z < newDimZ; z++)
+ for (int x = 0; x < halfDimX; x++)
+ {
+ for (int y = 0; y < halfDimY; y++)
{
- for (int y = 0; y < newDimY; y++)
+ for (int z = 0; z < halfDimZ; z++)
{
- for (int x = 0; x < newDimX; x++)
- {
- int oldIndex = (z * dimX * dimY) + (y * dimX) + x;
- int newIndex = (z * newDimX * newDimY) + (y * newDimX) + x;
- newData[newIndex] = data[oldIndex];
- }
+ downScaledData[x + y * halfDimX + z * (halfDimX * halfDimY)] = Mathf.RoundToInt(GetAvgerageVoxelValues(x * 2, y * 2, z * 2));
}
}
- data = newData;
- dimX = newDimX;
- dimY = newDimY;
- dimZ = newDimZ;
}
+
+ //Update data & data dimensions
+ data = downScaledData;
+ dimX = halfDimX;
+ dimY = halfDimY;
+ dimZ = halfDimZ;
}
private void CalculateValueBounds()
@@ -214,117 +206,6 @@ namespace UnityVolumeRendering
return texture;
}
-
- private Texture3D CreateDownScaledTextureInternal()
- {
- halfDimX = dimX / 2 + dimX % 2;
- halfDimY = dimY / 2 + dimY % 2;
- halfDimZ = dimZ / 2 + dimZ % 2;
-
- TextureFormat texformat = SystemInfo.SupportsTextureFormat(TextureFormat.RHalf) ? TextureFormat.RHalf : TextureFormat.RFloat;
-
- Texture3D texture = new Texture3D(halfDimX, halfDimY, halfDimZ, texformat, false);
- texture.wrapMode = TextureWrapMode.Clamp;
-
- downScaledData = new float[halfDimX * halfDimY * halfDimZ];
-
- int minValue = GetMinDataValue();
- int maxValue = GetMaxDataValue();
- int maxRange = maxValue - minValue;
-
- bool isHalfFloat = texformat == TextureFormat.RHalf;
- int sampleSize = isHalfFloat ? 2 : 4;
- byte[] bytes;
- try
- {
- // Create a byte array for filling the texture. Store has half (16 bit) or single (32 bit) float values.
- bytes = new byte[halfDimX * halfDimY * halfDimZ * sampleSize]; // This can cause OutOfMemoryException
- }
- catch (OutOfMemoryException ex)
- {
- Debug.LogWarning("Out of memory when creating texture. Using fallback method.");
- bytes = null;
- }
-
- for (int x = 0; x < halfDimX; x++)
- {
- for (int y = 0; y < halfDimY; y++)
- {
- for (int z = 0; z < halfDimZ; z++)
- {
- float avg = GetAvgerageVoxelValues(x * 2, y * 2, z * 2);
- int index = x + y * halfDimX + z * (halfDimX * halfDimY);
- downScaledData[index] = (avg - minValue) / maxRange; //Store downscaled data for gradient texture generation, etc.
-
- if (bytes == null)
- {
- texture.SetPixel(x, y, z, new Color(downScaledData[index], 0.0f, 0.0f, 0.0f));
- }
- else
- {
- byte[] pixelBytes = isHalfFloat ? BitConverter.GetBytes(Mathf.FloatToHalf(downScaledData[index])) : BitConverter.GetBytes(downScaledData[index]);
- Array.Copy(pixelBytes, 0, bytes, index * sampleSize, sampleSize);
- }
- }
- }
- }
-
- if (bytes != null) texture.SetPixelData(bytes, 0);
-
- texture.Apply();
- return texture;
- }
-
- private Texture3D CreateDownScaledGradientTextureInternal()
- {
- if (!downScaled || downScaledData == null) throw new InvalidOperationException("Downscaled gradient texture must be generated after downscaled main texture!");
-
- TextureFormat texformat = SystemInfo.SupportsTextureFormat(TextureFormat.RGBAHalf) ? TextureFormat.RGBAHalf : TextureFormat.RGBAFloat;
- Texture3D texture = new Texture3D(halfDimX, halfDimY, halfDimZ, texformat, false);
- texture.wrapMode = TextureWrapMode.Clamp;
-
- Color[] cols;
- try
- {
- cols = new Color[downScaledData.Length];
- }
- catch (OutOfMemoryException ex)
- {
- cols = null;
- }
- for (int x = 0; x < halfDimX; x++)
- {
- for (int y = 0; y < halfDimY; y++)
- {
- for (int z = 0; z < halfDimZ; z++)
- {
- int iData = x + y * halfDimX + z * (halfDimX * halfDimY);
-
- float x1 = downScaledData[Math.Min(x + 1, halfDimX - 1) + y * halfDimX + z * (halfDimX * halfDimY)];
- float x2 = downScaledData[Math.Max(x - 1, 0) + y * halfDimX + z * (halfDimX * halfDimY)];
- float y1 = downScaledData[x + Math.Min(y + 1, halfDimY - 1) * halfDimX + z * (halfDimX * halfDimY)];
- float y2 = downScaledData[x + Math.Max(y - 1, 0) * halfDimX + z * (halfDimX * halfDimY)];
- float z1 = downScaledData[x + y * halfDimX + Math.Min(z + 1, halfDimZ - 1) * (halfDimX * halfDimY)];
- float z2 = downScaledData[x + y * halfDimX + Math.Max(z - 1, 0) * (halfDimX * halfDimY)];
-
- Vector3 grad = new Vector3(x2 - x1, y2 - y1, z2 - z1);
-
- if (cols == null)
- {
- texture.SetPixel(x, y, z, new Color(grad.x, grad.y, grad.z, downScaledData[iData]));
- }
- else
- {
- cols[iData] = new Color(grad.x, grad.y, grad.z, downScaledData[iData]);
- }
- }
- }
- }
- if (cols != null) texture.SetPixels(cols);
- texture.Apply();
- return texture;
- }
-
public float GetAvgerageVoxelValues(int x, int y, int z)
{
// if a dimension length is not an even number
@@ -351,9 +232,5 @@ namespace UnityVolumeRendering
{
return data[x + y * dimX + z * (dimX * dimY)];
}
- public float GetDownScaledData(int x, int y, int z)
- {
- return downScaledData[x + y * halfDimX + z * (halfDimX * halfDimY)];
- }
}
}
From 5f0d380920d5c5d793d50fe1776bc7b4b627ef06 Mon Sep 17 00:00:00 2001
From: Vahid <92767644+vahpy@users.noreply.github.com>
Date: Sat, 23 Oct 2021 22:20:08 +1100
Subject: [PATCH 2/3] Added optional downscaled option
---
Assets/Scripts/Importing/DICOMImporter.cs | 18 +++++++++++++-----
.../Scripts/Importing/ImageSequenceImporter.cs | 10 +++++++++-
Assets/Scripts/Importing/RawDatasetImporter.cs | 14 ++++++++++----
3 files changed, 32 insertions(+), 10 deletions(-)
diff --git a/Assets/Scripts/Importing/DICOMImporter.cs b/Assets/Scripts/Importing/DICOMImporter.cs
index 84ff334..61bacff 100644
--- a/Assets/Scripts/Importing/DICOMImporter.cs
+++ b/Assets/Scripts/Importing/DICOMImporter.cs
@@ -65,7 +65,10 @@ namespace UnityVolumeRendering
// Load all DICOM files
List files = new List();
- foreach (string filePath in fileCandidates)
+
+ IEnumerable sortedFiles = fileCandidates.OrderBy(s => s);
+
+ foreach (string filePath in sortedFiles)
{
DICOMSliceFile sliceFile = ReadDICOMFile(filePath);
if(sliceFile != null)
@@ -90,13 +93,10 @@ namespace UnityVolumeRendering
return new List(seriesByUID.Values);
}
- public VolumeDataset ImportDICOMSeries(DICOMSeries series)
+ public VolumeDataset ImportDICOMSeries(DICOMSeries series, bool forceDownScaling = false)
{
List files = series.dicomFiles;
- // Sort files by slice location
- files.Sort((DICOMSliceFile a, DICOMSliceFile b) => { return a.location.CompareTo(b.location); });
-
// Check if the series is missing the slice location tag
bool needsCalcLoc = false;
foreach (DICOMSliceFile file in files)
@@ -107,6 +107,9 @@ namespace UnityVolumeRendering
// Calculate slice location from "Image Position" (0020,0032)
if (needsCalcLoc)
CalcSliceLocFromPos(files);
+
+ // Sort files by slice location
+ files.Sort((DICOMSliceFile a, DICOMSliceFile b) => { return a.location.CompareTo(b.location); });
Debug.Log($"Importing {files.Count} DICOM slices");
@@ -156,6 +159,11 @@ namespace UnityVolumeRendering
dataset.scaleZ = Mathf.Abs(files[files.Count - 1].location - files[0].location);
}
+ if (forceDownScaling)
+ {
+ dataset.DownScaleData();
+ }
+
dataset.FixDimensions();
return dataset;
diff --git a/Assets/Scripts/Importing/ImageSequenceImporter.cs b/Assets/Scripts/Importing/ImageSequenceImporter.cs
index 604c08e..887a445 100644
--- a/Assets/Scripts/Importing/ImageSequenceImporter.cs
+++ b/Assets/Scripts/Importing/ImageSequenceImporter.cs
@@ -11,15 +11,18 @@ namespace UnityVolumeRendering
public class ImageSequenceImporter
{
private string directoryPath;
+ private bool forceDownScaling;
private string[] supportedImageTypes = new string[]
{
"*.png",
"*.jpg",
+ "*.jpeg"
};
- public ImageSequenceImporter(string directoryPath)
+ public ImageSequenceImporter(string directoryPath, bool forceDownScaling = false)
{
this.directoryPath = directoryPath;
+ this.forceDownScaling = forceDownScaling;
}
public VolumeDataset Import()
@@ -36,6 +39,11 @@ namespace UnityVolumeRendering
int[] data = FillSequentialData(dimensions, imagePaths);
VolumeDataset dataset = FillVolumeDataset(data, dimensions);
+ if (forceDownScaling)
+ {
+ dataset.DownScaleData();
+ }
+
dataset.FixDimensions();
return dataset;
diff --git a/Assets/Scripts/Importing/RawDatasetImporter.cs b/Assets/Scripts/Importing/RawDatasetImporter.cs
index 49ea545..54a58e6 100644
--- a/Assets/Scripts/Importing/RawDatasetImporter.cs
+++ b/Assets/Scripts/Importing/RawDatasetImporter.cs
@@ -29,8 +29,8 @@ namespace UnityVolumeRendering
private DataContentFormat contentFormat;
private Endianness endianness;
private int skipBytes;
-
- public RawDatasetImporter(string filePath, int dimX, int dimY, int dimZ, DataContentFormat contentFormat, Endianness endianness, int skipBytes)
+ private bool forceDownScaling;
+ public RawDatasetImporter(string filePath, int dimX, int dimY, int dimZ, DataContentFormat contentFormat, Endianness endianness, int skipBytes, bool forceDownScaling = false)
{
this.filePath = filePath;
this.dimX = dimX;
@@ -39,12 +39,13 @@ namespace UnityVolumeRendering
this.contentFormat = contentFormat;
this.endianness = endianness;
this.skipBytes = skipBytes;
+ this.forceDownScaling = forceDownScaling;
}
public VolumeDataset Import()
{
// Check that the file exists
- if(!File.Exists(filePath))
+ if (!File.Exists(filePath))
{
Debug.LogError("The file does not exist: " + filePath);
return null;
@@ -86,8 +87,13 @@ namespace UnityVolumeRendering
reader.Close();
fs.Close();
+ if (forceDownScaling)
+ {
+ dataset.DownScaleData();
+ }
+
dataset.FixDimensions();
-
+
return dataset;
}
From a36f01e819c4c0415bc5b40a27db4905d56d58ac Mon Sep 17 00:00:00 2001
From: Vahid <92767644+vahpy@users.noreply.github.com>
Date: Sat, 23 Oct 2021 22:35:41 +1100
Subject: [PATCH 3/3] Added popup dialog for optional downscaling
---
.../Editor/RAWDatasetImporterEditorWIndow.cs | 13 +++++++++--
.../Editor/VolumeRendererEditorFunctions.cs | 22 +++++++++++++++++--
2 files changed, 31 insertions(+), 4 deletions(-)
diff --git a/Assets/Editor/RAWDatasetImporterEditorWIndow.cs b/Assets/Editor/RAWDatasetImporterEditorWIndow.cs
index 6398d1a..9916864 100644
--- a/Assets/Editor/RAWDatasetImporterEditorWIndow.cs
+++ b/Assets/Editor/RAWDatasetImporterEditorWIndow.cs
@@ -43,8 +43,17 @@ namespace UnityVolumeRendering
private void ImportDataset()
{
- RawDatasetImporter importer = new RawDatasetImporter(fileToImport, dimX, dimY, dimZ, dataFormat, endianness, bytesToSkip);
-
+ RawDatasetImporter importer;
+ if (EditorUtility.DisplayDialog("Optional DownScaling",
+ "Do you want to downscale texture even if the dimensions are within the limits?", "Yes", "No"))
+ {
+ importer = new RawDatasetImporter(fileToImport, dimX, dimY, dimZ, dataFormat, endianness, bytesToSkip, true);
+ }
+ else
+ {
+ importer = new RawDatasetImporter(fileToImport, dimX, dimY, dimZ, dataFormat, endianness, bytesToSkip, false);
+ }
+
VolumeDataset dataset = importer.Import();
if (dataset != null)
diff --git a/Assets/Editor/VolumeRendererEditorFunctions.cs b/Assets/Editor/VolumeRendererEditorFunctions.cs
index 3305308..b36c4d5 100644
--- a/Assets/Editor/VolumeRendererEditorFunctions.cs
+++ b/Assets/Editor/VolumeRendererEditorFunctions.cs
@@ -51,9 +51,17 @@ namespace UnityVolumeRendering
DICOMImporter importer = new DICOMImporter(fileCandidates, Path.GetFileName(dir));
List seriesList = importer.LoadDICOMSeries();
float numVolumesCreated = 0;
+
+ bool forceDownScaling = false;
+ if (EditorUtility.DisplayDialog("Optional DownScaling",
+ "Do you want to downscale texture even if the dimensions are within the limits?", "Yes", "No"))
+ {
+ forceDownScaling = true;
+ }
+
foreach (DICOMImporter.DICOMSeries series in seriesList)
{
- VolumeDataset dataset = importer.ImportDICOMSeries(series);
+ VolumeDataset dataset = importer.ImportDICOMSeries(series, forceDownScaling);
if (dataset != null)
{
VolumeRenderedObject obj = VolumeObjectFactory.CreateObject(dataset);
@@ -77,9 +85,19 @@ namespace UnityVolumeRendering
static void ShowSequenceImporter()
{
string dir = EditorUtility.OpenFolderPanel("Select a folder to load", "", "");
+
if (Directory.Exists(dir))
{
- ImageSequenceImporter importer = new ImageSequenceImporter(dir);
+ ImageSequenceImporter importer;
+ if (EditorUtility.DisplayDialog("Optional DownScaling",
+ "Do you want to downscale texture even if the dimensions are within the limits?", "Yes", "No"))
+ {
+ importer = new ImageSequenceImporter(dir,true);
+ }
+ else
+ {
+ importer = new ImageSequenceImporter(dir,false);
+ }
VolumeDataset dataset = importer.Import();
if (dataset != null)
VolumeObjectFactory.CreateObject(dataset);