Merge pull request #2 from vahpy/vahpy-new-features
Simplified downscaling + popup dialog
This commit is contained in:
Коммит
48769ac591
|
@ -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)
|
||||
|
|
|
@ -51,9 +51,17 @@ namespace UnityVolumeRendering
|
|||
DICOMImporter importer = new DICOMImporter(fileCandidates, Path.GetFileName(dir));
|
||||
List<DICOMImporter.DICOMSeries> 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);
|
||||
|
|
|
@ -93,7 +93,7 @@ namespace UnityVolumeRendering
|
|||
return new List<DICOMSeries>(seriesByUID.Values);
|
||||
}
|
||||
|
||||
public VolumeDataset ImportDICOMSeries(DICOMSeries series)
|
||||
public VolumeDataset ImportDICOMSeries(DICOMSeries series, bool forceDownScaling = false)
|
||||
{
|
||||
List<DICOMSliceFile> files = series.dicomFiles;
|
||||
|
||||
|
@ -159,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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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];
|
||||
/// <summary>
|
||||
/// Downscales the data by averaging 8 voxels per each new voxel,
|
||||
/// and replaces downscaled data with the original data
|
||||
/// </summary>
|
||||
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)];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче