Merge branch 'master' into PARCHG---Electron-Density-Volume-Renderer
This commit is contained in:
Коммит
54ce3d6217
|
@ -434,6 +434,25 @@ namespace openDicom.Registry
|
|||
if (global == null) Global = this;
|
||||
}
|
||||
|
||||
public void LoadFromMemory(MemoryStream memStream, DictionaryFileFormat fileFormat)
|
||||
{
|
||||
if (!IsEmpty) Clear();
|
||||
StreamReader streamReader = new StreamReader(memStream);
|
||||
switch (fileFormat)
|
||||
{
|
||||
case DictionaryFileFormat.BinaryFile:
|
||||
LoadFromBinary(streamReader); break;
|
||||
case DictionaryFileFormat.PropertyFile:
|
||||
LoadFromProperty(streamReader); break;
|
||||
case DictionaryFileFormat.CsvFile:
|
||||
LoadFromCsv(streamReader); break;
|
||||
case DictionaryFileFormat.XmlFile:
|
||||
LoadFromXml(streamReader); break;
|
||||
}
|
||||
streamReader.Close();
|
||||
if (global == null) Global = this;
|
||||
}
|
||||
|
||||
private void SaveAsBinary(StreamWriter streamWriter,
|
||||
DataElementDictionaryEntry[] entryArray)
|
||||
{
|
||||
|
|
|
@ -308,6 +308,25 @@ namespace openDicom.Registry
|
|||
if (global == null) Global = this;
|
||||
}
|
||||
|
||||
public void LoadFromMemory(MemoryStream memStream, DictionaryFileFormat fileFormat)
|
||||
{
|
||||
if (!IsEmpty) Clear();
|
||||
StreamReader streamReader = new StreamReader(memStream);
|
||||
switch (fileFormat)
|
||||
{
|
||||
case DictionaryFileFormat.BinaryFile:
|
||||
LoadFromBinary(streamReader); break;
|
||||
case DictionaryFileFormat.PropertyFile:
|
||||
LoadFromProperty(streamReader); break;
|
||||
case DictionaryFileFormat.CsvFile:
|
||||
LoadFromCsv(streamReader); break;
|
||||
case DictionaryFileFormat.XmlFile:
|
||||
LoadFromXml(streamReader); break;
|
||||
}
|
||||
streamReader.Close();
|
||||
if (global == null) Global = this;
|
||||
}
|
||||
|
||||
private void SaveAsBinary(StreamWriter streamWriter,
|
||||
UidDictionaryEntry[] entryArray)
|
||||
{
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityVolumeRendering
|
||||
{
|
||||
public class ImportSettingsEditorWindow : EditorWindow
|
||||
{
|
||||
public static void ShowWindow()
|
||||
{
|
||||
ImportSettingsEditorWindow wnd = new ImportSettingsEditorWindow();
|
||||
wnd.Show();
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
EditorGUILayout.LabelField("Show promt asking if you want to downscale the dataset on import?");
|
||||
bool showDownscalePrompt = EditorGUILayout.Toggle("Show downscale prompt", EditorPrefs.GetBool("DownscaleDatasetPrompt"));
|
||||
EditorPrefs.SetBool("DownscaleDatasetPrompt", showDownscalePrompt);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 8967c87c2996b4a4988102a8f6eaa239
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -43,12 +43,19 @@ namespace UnityVolumeRendering
|
|||
|
||||
private void ImportDataset()
|
||||
{
|
||||
RawDatasetImporter importer = new RawDatasetImporter(fileToImport, dimX, dimY, dimZ, dataFormat, endianness, bytesToSkip);
|
||||
|
||||
RawDatasetImporter importer = new RawDatasetImporter(fileToImport, dimX, dimY, dimZ, dataFormat, endianness, bytesToSkip);
|
||||
VolumeDataset dataset = importer.Import();
|
||||
|
||||
if (dataset != null)
|
||||
{
|
||||
if (EditorPrefs.GetBool("DownscaleDatasetPrompt"))
|
||||
{
|
||||
if (EditorUtility.DisplayDialog("Optional DownScaling",
|
||||
$"Do you want to downscale the dataset? The dataset's dimension is: {dataset.dimX} x {dataset.dimY} x {dataset.dimZ}", "Yes", "No"))
|
||||
{
|
||||
dataset.DownScaleData();
|
||||
}
|
||||
}
|
||||
VolumeRenderedObject obj = VolumeObjectFactory.CreateObject(dataset);
|
||||
}
|
||||
else
|
||||
|
|
|
@ -93,10 +93,10 @@ namespace UnityVolumeRendering
|
|||
TransferFunction2D.TF2DBox box = tf2d.boxes[selectedBoxIndex];
|
||||
float oldX = box.rect.x;
|
||||
float oldY = box.rect.y;
|
||||
box.rect.x = EditorGUI.Slider(new Rect(startX, startY, 200.0f, 20.0f), "x", box.rect.x, 0.0f, 0.99f);
|
||||
box.rect.width = EditorGUI.Slider(new Rect(startX + 220.0f, startY, 200.0f, 20.0f), "width", oldX + box.rect.width, box.rect.x + 0.01f, 1.0f) - box.rect.x;
|
||||
box.rect.y = EditorGUI.Slider(new Rect(startX, startY + 50, 200.0f, 20.0f), "y", box.rect.y, 0.0f, 1.0f);
|
||||
box.rect.height = EditorGUI.Slider(new Rect(startX + 220.0f, startY + 50, 200.0f, 20.0f), "height", oldY + box.rect.height, box.rect.y + 0.01f, 1.0f) - box.rect.y;
|
||||
box.rect.x = EditorGUI.Slider(new Rect(startX, startY, 200.0f, 20.0f), "min x", box.rect.x, 0.0f, 0.99f);
|
||||
box.rect.width = EditorGUI.Slider(new Rect(startX + 220.0f, startY, 200.0f, 20.0f), "max x", oldX + box.rect.width, box.rect.x + 0.01f, 1.0f) - box.rect.x;
|
||||
box.rect.y = EditorGUI.Slider(new Rect(startX, startY + 50, 200.0f, 20.0f), "min y", box.rect.y, 0.0f, 1.0f);
|
||||
box.rect.height = EditorGUI.Slider(new Rect(startX + 220.0f, startY + 50, 200.0f, 20.0f), "max y", oldY + box.rect.height, box.rect.y + 0.01f, 1.0f) - box.rect.y;
|
||||
box.colour = EditorGUI.ColorField(new Rect(startX + 450.0f, startY + 10, 100.0f, 20.0f), box.colour);
|
||||
box.minAlpha = EditorGUI.Slider(new Rect(startX + 450.0f, startY + 30, 200.0f, 20.0f), "min alpha", box.minAlpha, 0.0f, 1.0f);
|
||||
box.alpha = EditorGUI.Slider(new Rect(startX + 450.0f, startY + 60, 200.0f, 20.0f), "max alpha", box.alpha, 0.0f, 1.0f);
|
||||
|
@ -110,7 +110,7 @@ namespace UnityVolumeRendering
|
|||
}
|
||||
|
||||
// Add new rectangle
|
||||
if (GUI.Button(new Rect(startX, startY + 100, 110.0f, 30.0f), "Add rectangle"))
|
||||
if (GUI.Button(new Rect(startX, startY + 100, 150.0f, 30.0f), "Add rectangle"))
|
||||
{
|
||||
tf2d.AddBox(0.1f, 0.1f, 0.8f, 0.8f, Color.white, 0.5f);
|
||||
needsRegenTexture = true;
|
||||
|
@ -118,20 +118,21 @@ namespace UnityVolumeRendering
|
|||
// Remove selected shape
|
||||
if (selectedBoxIndex != -1)
|
||||
{
|
||||
if (GUI.Button(new Rect(startX, startY + 140, 110.0f, 30.0f), "Remove selected shape"))
|
||||
if (GUI.Button(new Rect(startX, startY + 140, 150.0f, 30.0f), "Remove selected shape"))
|
||||
{
|
||||
tf2d.boxes.RemoveAt(selectedBoxIndex);
|
||||
selectedBoxIndex = -1;
|
||||
needsRegenTexture = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(GUI.Button(new Rect(startX, startY + 180, 110.0f, 30.0f), "Save"))
|
||||
if(GUI.Button(new Rect(startX, startY + 180, 150.0f, 30.0f), "Save"))
|
||||
{
|
||||
string filepath = EditorUtility.SaveFilePanel("Save transfer function", "", "default.tf2d", "tf2d");
|
||||
if(filepath != "")
|
||||
TransferFunctionDatabase.SaveTransferFunction2D(tf2d, filepath);
|
||||
}
|
||||
if(GUI.Button(new Rect(startX, startY + 220, 110.0f, 30.0f), "Load"))
|
||||
if(GUI.Button(new Rect(startX, startY + 220, 150.0f, 30.0f), "Load"))
|
||||
{
|
||||
string filepath = EditorUtility.OpenFilePanel("Save transfer function", "", "tf2d");
|
||||
if(filepath != "")
|
||||
|
@ -151,8 +152,6 @@ namespace UnityVolumeRendering
|
|||
tf2d.GenerateTexture();
|
||||
needsRegenTexture = false;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
private void OnSelectionChange()
|
||||
|
|
|
@ -65,11 +65,21 @@ namespace UnityVolumeRendering
|
|||
DICOMImporter importer = new DICOMImporter(fileCandidates, Path.GetFileName(dir));
|
||||
List<DICOMImporter.DICOMSeries> seriesList = importer.LoadDICOMSeries();
|
||||
float numVolumesCreated = 0;
|
||||
|
||||
foreach (DICOMImporter.DICOMSeries series in seriesList)
|
||||
{
|
||||
VolumeDataset dataset = importer.ImportDICOMSeries(series);
|
||||
if (dataset != null)
|
||||
{
|
||||
if (EditorPrefs.GetBool("DownscaleDatasetPrompt"))
|
||||
{
|
||||
if (EditorUtility.DisplayDialog("Optional DownScaling",
|
||||
$"Do you want to downscale the dataset? The dataset's dimension is: {dataset.dimX} x {dataset.dimY} x {dataset.dimZ}", "Yes", "No"))
|
||||
{
|
||||
dataset.DownScaleData();
|
||||
}
|
||||
}
|
||||
|
||||
VolumeRenderedObject obj = VolumeObjectFactory.CreateObject(dataset);
|
||||
obj.transform.position = new Vector3(numVolumesCreated, 0, 0);
|
||||
numVolumesCreated++;
|
||||
|
@ -91,12 +101,24 @@ namespace UnityVolumeRendering
|
|||
static void ShowSequenceImporter()
|
||||
{
|
||||
string dir = EditorUtility.OpenFolderPanel("Select a folder to load", "", "");
|
||||
|
||||
if (Directory.Exists(dir))
|
||||
{
|
||||
ImageSequenceImporter importer = new ImageSequenceImporter(dir);
|
||||
|
||||
VolumeDataset dataset = importer.Import();
|
||||
if (dataset != null)
|
||||
{
|
||||
if (EditorPrefs.GetBool("DownscaleDatasetPrompt"))
|
||||
{
|
||||
if (EditorUtility.DisplayDialog("Optional DownScaling",
|
||||
$"Do you want to downscale the dataset? The dataset's dimension is: {dataset.dimX} x {dataset.dimY} x {dataset.dimZ}", "Yes", "No"))
|
||||
{
|
||||
dataset.DownScaleData();
|
||||
}
|
||||
}
|
||||
VolumeObjectFactory.CreateObject(dataset);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -148,5 +170,11 @@ namespace UnityVolumeRendering
|
|||
{
|
||||
ValueRangeEditorWindow.ShowWindow();
|
||||
}
|
||||
|
||||
[MenuItem("Volume Rendering/Settigs")]
|
||||
static void ShowSettingsWindow()
|
||||
{
|
||||
ImportSettingsEditorWindow.ShowWindow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 973e06d586332ae47bed697dfab2de3d
|
||||
DefaultImporter:
|
||||
guid: 995b791e7a90b494194aa1d02cedc49c
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
|
@ -1,6 +1,6 @@
|
|||
fileFormatVersion: 2
|
||||
guid: dbc53a7688d64154eb54ec48178d29dd
|
||||
DefaultImporter:
|
||||
guid: 53bd64257ce5dc444ba9bc04d58de1e7
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
|
@ -54,8 +54,14 @@ namespace UnityVolumeRendering
|
|||
UidDictionary uidDictionary = new UidDictionary();
|
||||
try
|
||||
{
|
||||
dataElementDictionary.LoadFrom(Path.Combine(Application.streamingAssetsPath, "dicom-elements-2007.dic"), DictionaryFileFormat.BinaryFile);
|
||||
uidDictionary.LoadFrom(Path.Combine(Application.streamingAssetsPath, "dicom-uids-2007.dic"), DictionaryFileFormat.BinaryFile);
|
||||
// Load .dic files from Resources
|
||||
TextAsset dcmElemAsset = (TextAsset)Resources.Load("dicom-elements-2007.dic");
|
||||
Debug.Assert(dcmElemAsset != null, "dicom-elements-2007.dic is missing from the Resources folder");
|
||||
TextAsset dcmUidsAsset = (TextAsset)Resources.Load("dicom-uids-2007.dic");
|
||||
Debug.Assert(dcmUidsAsset != null, "dicom-uids-2007.dic is missing from the Resources folder");
|
||||
|
||||
dataElementDictionary.LoadFromMemory(new MemoryStream(dcmElemAsset.bytes), DictionaryFileFormat.BinaryFile);
|
||||
uidDictionary.LoadFromMemory(new MemoryStream(dcmUidsAsset.bytes), DictionaryFileFormat.BinaryFile);
|
||||
}
|
||||
catch (Exception dictionaryException)
|
||||
{
|
||||
|
@ -65,7 +71,10 @@ namespace UnityVolumeRendering
|
|||
|
||||
// Load all DICOM files
|
||||
List<DICOMSliceFile> files = new List<DICOMSliceFile>();
|
||||
foreach (string filePath in fileCandidates)
|
||||
|
||||
IEnumerable<string> sortedFiles = fileCandidates.OrderBy(s => s);
|
||||
|
||||
foreach (string filePath in sortedFiles)
|
||||
{
|
||||
DICOMSliceFile sliceFile = ReadDICOMFile(filePath);
|
||||
if(sliceFile != null)
|
||||
|
@ -94,9 +103,6 @@ namespace UnityVolumeRendering
|
|||
{
|
||||
List<DICOMSliceFile> 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 +113,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");
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ namespace UnityVolumeRendering
|
|||
{
|
||||
"*.png",
|
||||
"*.jpg",
|
||||
"*.jpeg"
|
||||
};
|
||||
|
||||
public ImageSequenceImporter(string directoryPath)
|
||||
|
@ -29,9 +30,6 @@ namespace UnityVolumeRendering
|
|||
|
||||
List<string> imagePaths = GetSortedImagePaths();
|
||||
|
||||
if (!ImageSetHasUniformDimensions(imagePaths))
|
||||
throw new IndexOutOfRangeException("Image sequence has non-uniform dimensions");
|
||||
|
||||
Vector3Int dimensions = GetVolumeDimensions(imagePaths);
|
||||
int[] data = FillSequentialData(dimensions, imagePaths);
|
||||
VolumeDataset dataset = FillVolumeDataset(data, dimensions);
|
||||
|
@ -59,34 +57,6 @@ namespace UnityVolumeRendering
|
|||
return imagePaths;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if every image in the set has the same XY dimensions.
|
||||
/// </summary>
|
||||
/// <param name="imagePaths">The list of image paths to check.</param>
|
||||
/// <returns>True if at least one image differs from another.</returns>
|
||||
private bool ImageSetHasUniformDimensions(List<string> imagePaths)
|
||||
{
|
||||
bool hasUniformDimension = true;
|
||||
|
||||
Vector2Int previous, current;
|
||||
previous = GetImageDimensions(imagePaths[0]);
|
||||
|
||||
foreach (var path in imagePaths)
|
||||
{
|
||||
current = GetImageDimensions(path);
|
||||
|
||||
if (current.x != previous.x || current.y != previous.y)
|
||||
{
|
||||
hasUniformDimension = false;
|
||||
break;
|
||||
}
|
||||
|
||||
previous = current;
|
||||
}
|
||||
|
||||
return hasUniformDimension;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the XY dimensions of an image at the path.
|
||||
/// </summary>
|
||||
|
@ -141,6 +111,12 @@ namespace UnityVolumeRendering
|
|||
byte[] bytes = File.ReadAllBytes(path);
|
||||
texture.LoadImage(bytes);
|
||||
|
||||
if (texture.width != dimensions.x || texture.height != dimensions.y)
|
||||
{
|
||||
Texture2D.DestroyImmediate(texture);
|
||||
throw new IndexOutOfRangeException("Image sequence has non-uniform dimensions");
|
||||
}
|
||||
|
||||
Color[] pixels = texture.GetPixels(); // Order priority: X -> Y -> Z
|
||||
int[] imageData = DensityHelper.ConvertColorsToDensities(pixels);
|
||||
|
||||
|
@ -176,4 +152,4 @@ namespace UnityVolumeRendering
|
|||
return dataset;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,7 +29,6 @@ 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)
|
||||
{
|
||||
this.filePath = filePath;
|
||||
|
@ -44,7 +43,7 @@ namespace UnityVolumeRendering
|
|||
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;
|
||||
|
@ -88,7 +87,7 @@ namespace UnityVolumeRendering
|
|||
fs.Close();
|
||||
|
||||
dataset.FixDimensions();
|
||||
|
||||
|
||||
return dataset;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,153 +1,169 @@
|
|||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityVolumeRendering
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility class for generating histograms fo rthe dataset.
|
||||
/// </summary>
|
||||
public class HistogramTextureGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Generates a histogram where:
|
||||
/// X-axis = the data sample (density) value
|
||||
/// Y-axis = the sample count (number of data samples with the specified density)
|
||||
/// </summary>
|
||||
/// <param name="dataset"></param>
|
||||
/// <returns></returns>
|
||||
public static Texture2D GenerateHistogramTexture(VolumeDataset dataset)
|
||||
{
|
||||
float minValue = dataset.GetMinDataValue();
|
||||
float maxValue = dataset.GetMaxDataValue();
|
||||
float valueRange = maxValue - minValue;
|
||||
|
||||
int numFrequencies = Mathf.Min((int)valueRange, 1024);
|
||||
int[] frequencies = new int[numFrequencies];
|
||||
|
||||
int maxFreq = 0;
|
||||
float valRangeRecip = 1.0f / (maxValue - minValue);
|
||||
for (int iData = 0; iData < dataset.data.Length; iData++)
|
||||
{
|
||||
float dataValue = dataset.data[iData];
|
||||
float tValue = (dataValue - minValue) * valRangeRecip;
|
||||
int freqIndex = (int)(tValue * (numFrequencies - 1));
|
||||
frequencies[freqIndex] += 1;
|
||||
maxFreq = System.Math.Max(frequencies[freqIndex], maxFreq);
|
||||
}
|
||||
|
||||
Color[] cols = new Color[numFrequencies];
|
||||
Texture2D texture = new Texture2D(numFrequencies, 1, TextureFormat.RGBAFloat, false);
|
||||
|
||||
for (int iSample = 0; iSample < numFrequencies; iSample++)
|
||||
cols[iSample] = new Color(Mathf.Log10((float)frequencies[iSample]) / Mathf.Log10((float)maxFreq), 0.0f, 0.0f, 1.0f);
|
||||
|
||||
texture.SetPixels(cols);
|
||||
//texture.filterMode = FilterMode.Point;
|
||||
texture.Apply();
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a histogram (but computaion is done on GPU) where:
|
||||
/// X-axis = the data sample (density) value
|
||||
/// Y-axis = the sample count (number of data samples with the specified density)
|
||||
/// </summary>
|
||||
/// <param name="dataset"></param>
|
||||
/// <returns></returns>
|
||||
public static Texture2D GenerateHistogramTextureOnGPU(VolumeDataset dataset)
|
||||
{
|
||||
double actualBound = dataset.GetMaxDataValue() - dataset.GetMinDataValue() + 1;
|
||||
int numValues = System.Convert.ToInt32(dataset.GetMaxDataValue() - dataset.GetMinDataValue() + 1); // removed +1
|
||||
int sampleCount = System.Math.Min(numValues, 256);
|
||||
|
||||
ComputeShader computeHistogram = Resources.Load("ComputeHistogram") as ComputeShader;
|
||||
int handleInitialize = computeHistogram.FindKernel("HistogramInitialize");
|
||||
int handleMain = computeHistogram.FindKernel("HistogramMain");
|
||||
|
||||
ComputeBuffer histogramBuffer = new ComputeBuffer(sampleCount, sizeof(uint) * 1);
|
||||
uint[] histogramData = new uint[sampleCount];
|
||||
Color32[] histogramCols = new Color32[sampleCount];
|
||||
|
||||
Texture3D dataTexture = dataset.GetDataTexture();
|
||||
|
||||
if (handleInitialize < 0 || handleMain < 0)
|
||||
{
|
||||
Debug.LogError("Histogram compute shader initialization failed.");
|
||||
}
|
||||
|
||||
computeHistogram.SetFloat("ValueRange", (float)(numValues - 1));
|
||||
computeHistogram.SetTexture(handleMain, "VolumeTexture", dataTexture);
|
||||
computeHistogram.SetBuffer(handleMain, "HistogramBuffer", histogramBuffer);
|
||||
computeHistogram.SetBuffer(handleInitialize, "HistogramBuffer", histogramBuffer);
|
||||
|
||||
computeHistogram.Dispatch(handleInitialize, sampleCount / 8, 1, 1);
|
||||
computeHistogram.Dispatch(handleMain, (dataTexture.width + 7) / 8, (dataTexture.height + 7) / 8, (dataTexture.depth + 7) / 8);
|
||||
|
||||
histogramBuffer.GetData(histogramData);
|
||||
|
||||
int maxValue = (int)histogramData.Max();
|
||||
|
||||
Texture2D texture = new Texture2D(sampleCount, 1, TextureFormat.RGBA32, false);
|
||||
for (int iSample = 0; iSample < sampleCount; iSample++)
|
||||
{
|
||||
histogramCols[iSample] = new Color(Mathf.Log10((float)histogramData[iSample]) / Mathf.Log10((float)maxValue), 0.0f, 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
texture.SetPixels32(histogramCols);
|
||||
texture.Apply();
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a histogram texture for 2D transfer functions.
|
||||
/// X-axis = data sample (density) value
|
||||
/// Y-axis = gradient magnitude
|
||||
/// colour = white (if there is a data sample with the specified value and gradient magnitude) or black (if not)
|
||||
/// </summary>
|
||||
/// <param name="dataset"></param>
|
||||
/// <returns></returns>
|
||||
public static Texture2D Generate2DHistogramTexture(VolumeDataset dataset)
|
||||
{
|
||||
int numSamples = (int)dataset.GetMaxDataValue() + 1;
|
||||
int numGradientSamples = 256;
|
||||
|
||||
Color[] cols = new Color[numSamples * numGradientSamples];
|
||||
Texture2D texture = new Texture2D(numSamples, numGradientSamples, TextureFormat.RGBAFloat, false);
|
||||
|
||||
for (int iCol = 0; iCol < cols.Length; iCol++)
|
||||
cols[iCol] = new Color(0.0f, 0.0f, 0.0f, 0.0f);
|
||||
|
||||
float maxRange = dataset.GetMaxDataValue() - dataset.GetMinDataValue();
|
||||
const float maxNormalisedMagnitude = 1.75f; // sqrt(1^2 + 1^2 + 1^2) = swrt(3) = a bit less than 1.75
|
||||
|
||||
for (int x = 1; x < dataset.dimX - 1; x++)
|
||||
{
|
||||
for (int y = 1; y < dataset.dimY - 1; y++)
|
||||
{
|
||||
for (int z = 1; z < dataset.dimZ - 1; z++)
|
||||
{
|
||||
int iData = x + y * dataset.dimX + z * (dataset.dimX * dataset.dimY);
|
||||
int density = Mathf.RoundToInt(dataset.data[iData]); // FIXME
|
||||
|
||||
float x1 = dataset.data[(x + 1) + y * dataset.dimX + z * (dataset.dimX * dataset.dimY)];
|
||||
float x2 = dataset.data[(x - 1) + y * dataset.dimX + z * (dataset.dimX * dataset.dimY)];
|
||||
float y1 = dataset.data[x + (y + 1) * dataset.dimX + z * (dataset.dimX * dataset.dimY)];
|
||||
float y2 = dataset.data[x + (y - 1) * dataset.dimX + z * (dataset.dimX * dataset.dimY)];
|
||||
float z1 = dataset.data[x + y * dataset.dimX + (z + 1) * (dataset.dimX * dataset.dimY)];
|
||||
float z2 = dataset.data[x + y * dataset.dimX + (z - 1) * (dataset.dimX * dataset.dimY)];
|
||||
|
||||
Vector3 grad = new Vector3((x2 - x1) / maxRange, (y2 - y1) / maxRange, (z2 - z1) / maxRange);
|
||||
cols[density + (int)(grad.magnitude * numGradientSamples / maxNormalisedMagnitude) * numSamples] = Color.white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
texture.SetPixels(cols);
|
||||
texture.Apply();
|
||||
|
||||
return texture;
|
||||
}
|
||||
}
|
||||
}
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityVolumeRendering
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility class for generating histograms fo rthe dataset.
|
||||
/// </summary>
|
||||
public class HistogramTextureGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Generates a histogram where:
|
||||
/// X-axis = the data sample (density) value
|
||||
/// Y-axis = the sample count (number of data samples with the specified density)
|
||||
/// </summary>
|
||||
/// <param name="dataset"></param>
|
||||
/// <returns></returns>
|
||||
public static Texture2D GenerateHistogramTexture(VolumeDataset dataset)
|
||||
{
|
||||
float minValue = dataset.GetMinDataValue();
|
||||
float maxValue = dataset.GetMaxDataValue();
|
||||
float valueRange = maxValue - minValue;
|
||||
|
||||
int numFrequencies = Mathf.Min((int)valueRange, 1024);
|
||||
int[] frequencies = new int[numFrequencies];
|
||||
|
||||
int maxFreq = 0;
|
||||
float valRangeRecip = 1.0f / (maxValue - minValue);
|
||||
for (int iData = 0; iData < dataset.data.Length; iData++)
|
||||
{
|
||||
float dataValue = dataset.data[iData];
|
||||
float tValue = (dataValue - minValue) * valRangeRecip;
|
||||
int freqIndex = (int)(tValue * (numFrequencies - 1));
|
||||
frequencies[freqIndex] += 1;
|
||||
maxFreq = System.Math.Max(frequencies[freqIndex], maxFreq);
|
||||
}
|
||||
|
||||
Color[] cols = new Color[numFrequencies];
|
||||
Texture2D texture = new Texture2D(numFrequencies, 1, TextureFormat.RGBAFloat, false);
|
||||
|
||||
for (int iSample = 0; iSample < numFrequencies; iSample++)
|
||||
cols[iSample] = new Color(Mathf.Log10((float)frequencies[iSample]) / Mathf.Log10((float)maxFreq), 0.0f, 0.0f, 1.0f);
|
||||
|
||||
texture.SetPixels(cols);
|
||||
//texture.filterMode = FilterMode.Point;
|
||||
texture.Apply();
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a histogram (but computaion is done on GPU) where:
|
||||
/// X-axis = the data sample (density) value
|
||||
/// Y-axis = the sample count (number of data samples with the specified density)
|
||||
/// </summary>
|
||||
/// <param name="dataset"></param>
|
||||
/// <returns></returns>
|
||||
public static Texture2D GenerateHistogramTextureOnGPU(VolumeDataset dataset)
|
||||
{
|
||||
double actualBound = dataset.GetMaxDataValue() - dataset.GetMinDataValue() + 1;
|
||||
int numValues = System.Convert.ToInt32(dataset.GetMaxDataValue() - dataset.GetMinDataValue() + 1); // removed +1
|
||||
int sampleCount = System.Math.Min(numValues, 256);
|
||||
|
||||
ComputeShader computeHistogram = Resources.Load("ComputeHistogram") as ComputeShader;
|
||||
int handleInitialize = computeHistogram.FindKernel("HistogramInitialize");
|
||||
int handleMain = computeHistogram.FindKernel("HistogramMain");
|
||||
|
||||
ComputeBuffer histogramBuffer = new ComputeBuffer(sampleCount, sizeof(uint) * 1);
|
||||
uint[] histogramData = new uint[sampleCount];
|
||||
Color32[] histogramCols = new Color32[sampleCount];
|
||||
|
||||
Texture3D dataTexture = dataset.GetDataTexture();
|
||||
|
||||
if (handleInitialize < 0 || handleMain < 0)
|
||||
{
|
||||
Debug.LogError("Histogram compute shader initialization failed.");
|
||||
}
|
||||
|
||||
computeHistogram.SetFloat("ValueRange", (float)(numValues - 1));
|
||||
computeHistogram.SetTexture(handleMain, "VolumeTexture", dataTexture);
|
||||
computeHistogram.SetBuffer(handleMain, "HistogramBuffer", histogramBuffer);
|
||||
computeHistogram.SetBuffer(handleInitialize, "HistogramBuffer", histogramBuffer);
|
||||
|
||||
computeHistogram.Dispatch(handleInitialize, sampleCount / 8, 1, 1);
|
||||
computeHistogram.Dispatch(handleMain, (dataTexture.width + 7) / 8, (dataTexture.height + 7) / 8, (dataTexture.depth + 7) / 8);
|
||||
|
||||
histogramBuffer.GetData(histogramData);
|
||||
|
||||
int maxValue = (int)histogramData.Max();
|
||||
|
||||
Texture2D texture = new Texture2D(sampleCount, 1, TextureFormat.RGBA32, false);
|
||||
for (int iSample = 0; iSample < sampleCount; iSample++)
|
||||
{
|
||||
histogramCols[iSample] = new Color(Mathf.Log10((float)histogramData[iSample]) / Mathf.Log10((float)maxValue), 0.0f, 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
texture.SetPixels32(histogramCols);
|
||||
texture.Apply();
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a histogram texture for 2D transfer functions.
|
||||
/// X-axis = data sample (density) value
|
||||
/// Y-axis = gradient magnitude
|
||||
/// colour = white (if there is a data sample with the specified value and gradient magnitude) or black (if not)
|
||||
/// </summary>
|
||||
/// <param name="dataset"></param>
|
||||
/// <returns></returns>
|
||||
public static Texture2D Generate2DHistogramTexture(VolumeDataset dataset)
|
||||
{
|
||||
int minValue = dataset.GetMinDataValue();
|
||||
int maxValue = dataset.GetMaxDataValue();
|
||||
|
||||
// Value range of the density values.
|
||||
int densityValRange = maxValue - minValue + 1;
|
||||
float densityRangeRecip = 1.0f / (maxValue - minValue); // reciprocal
|
||||
// Clamp density value samples.
|
||||
int numDensitySamples = System.Math.Min(densityValRange, 512);
|
||||
int numGradientSamples = 256;
|
||||
|
||||
Color[] cols = new Color[numDensitySamples * numGradientSamples];
|
||||
Texture2D texture = new Texture2D(numDensitySamples, numGradientSamples, TextureFormat.RGBAFloat, false);
|
||||
|
||||
// Zero-initialise colours.
|
||||
for (int iCol = 0; iCol < cols.Length; iCol++)
|
||||
cols[iCol] = new Color(0.0f, 0.0f, 0.0f, 0.0f);
|
||||
|
||||
float maxRange = dataset.GetMaxDataValue() - dataset.GetMinDataValue();
|
||||
const float maxNormalisedMagnitude = 1.75f; // sqrt(1^2 + 1^2 + 1^2) = swrt(3) = a bit less than 1.75
|
||||
|
||||
for (int x = 1; x < dataset.dimX - 1; x++)
|
||||
{
|
||||
for (int y = 1; y < dataset.dimY - 1; y++)
|
||||
{
|
||||
for (int z = 1; z < dataset.dimZ - 1; z++)
|
||||
{
|
||||
int iData = x + y * dataset.dimX + z * (dataset.dimX * dataset.dimY);
|
||||
int density = Mathf.RoundToInt(dataset.data[iData]); // FIXME
|
||||
|
||||
float x1 = dataset.data[(x + 1) + y * dataset.dimX + z * (dataset.dimX * dataset.dimY)];
|
||||
float x2 = dataset.data[(x - 1) + y * dataset.dimX + z * (dataset.dimX * dataset.dimY)];
|
||||
float y1 = dataset.data[x + (y + 1) * dataset.dimX + z * (dataset.dimX * dataset.dimY)];
|
||||
float y2 = dataset.data[x + (y - 1) * dataset.dimX + z * (dataset.dimX * dataset.dimY)];
|
||||
float z1 = dataset.data[x + y * dataset.dimX + (z + 1) * (dataset.dimX * dataset.dimY)];
|
||||
float z2 = dataset.data[x + y * dataset.dimX + (z - 1) * (dataset.dimX * dataset.dimY)];
|
||||
|
||||
// Calculate gradient
|
||||
Vector3 grad = new Vector3((x2 - x1) / (float)maxRange, (y2 - y1) / (float)maxRange, (z2 - z1) / (float)maxRange);
|
||||
|
||||
// Calculate density and gradient value indices (in flattened 2D array)
|
||||
float tDensity = (density - minValue) * densityRangeRecip;
|
||||
int iDensity = Mathf.RoundToInt((numDensitySamples - 1) * tDensity);
|
||||
int iGrad = (int)(grad.magnitude * numGradientSamples / maxNormalisedMagnitude);
|
||||
|
||||
// Assign a white colour to all samples (in a histogram where x = density and y = gradient magnitude).
|
||||
cols[iDensity + iGrad * numDensitySamples] = Color.white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
texture.SetPixels(cols);
|
||||
texture.Apply();
|
||||
|
||||
return texture;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,180 +1,239 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityVolumeRendering
|
||||
{
|
||||
/// <summary>
|
||||
/// An imported dataset. Has a dimension and a 3D pixel array.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class VolumeDataset : ScriptableObject
|
||||
{
|
||||
public string filePath;
|
||||
|
||||
// Flattened 3D array of data sample values.
|
||||
[SerializeField]
|
||||
public float[] data;
|
||||
|
||||
[SerializeField]
|
||||
public int dimX, dimY, dimZ;
|
||||
|
||||
[SerializeField]
|
||||
public float scaleX = 0.0f, scaleY = 0.0f, scaleZ = 0.0f;
|
||||
public float volumeScale;
|
||||
|
||||
[SerializeField]
|
||||
public string datasetName;
|
||||
|
||||
private float minDataValue = float.MaxValue;
|
||||
private float maxDataValue = float.MinValue;
|
||||
|
||||
private Texture3D dataTexture = null;
|
||||
private Texture3D gradientTexture = null;
|
||||
|
||||
|
||||
public Texture3D GetDataTexture()
|
||||
{
|
||||
dataTexture = CreateTextureInternal();
|
||||
return dataTexture;
|
||||
}
|
||||
|
||||
public Texture3D GetGradientTexture()
|
||||
{
|
||||
gradientTexture = CreateGradientTextureInternal();
|
||||
return gradientTexture;
|
||||
}
|
||||
|
||||
public float GetMinDataValue()
|
||||
{
|
||||
if (minDataValue == float.MaxValue)
|
||||
CalculateValueBounds();
|
||||
return minDataValue;
|
||||
}
|
||||
|
||||
public float GetMaxDataValue()
|
||||
{
|
||||
if (maxDataValue == float.MinValue)
|
||||
CalculateValueBounds();
|
||||
return maxDataValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that the dataset is not too large.
|
||||
/// </summary>
|
||||
public void FixDimensions()
|
||||
{
|
||||
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)
|
||||
{
|
||||
Debug.LogWarning("Dimension exceeds limits. Cropping dataset. This might result in an incomplete dataset.");
|
||||
|
||||
int newDimX = Mathf.Min(dimX, MAX_DIM);
|
||||
int newDimY = Mathf.Min(dimY, MAX_DIM);
|
||||
int newDimZ = Mathf.Min(dimZ, MAX_DIM);
|
||||
float[] newData = new float[dimX * dimY * dimZ];
|
||||
|
||||
for (int z = 0; z < newDimZ; z++)
|
||||
{
|
||||
for (int y = 0; y < newDimY; y++)
|
||||
{
|
||||
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];
|
||||
}
|
||||
}
|
||||
}
|
||||
data = newData;
|
||||
dimX = newDimX;
|
||||
dimY = newDimY;
|
||||
dimZ = newDimZ;
|
||||
}
|
||||
}
|
||||
|
||||
private void CalculateValueBounds()
|
||||
{
|
||||
minDataValue = float.MaxValue;
|
||||
maxDataValue = float.MinValue;
|
||||
|
||||
for (int i = 0; i < dimX * dimY * dimZ; i++)
|
||||
{
|
||||
if (data != null)
|
||||
{
|
||||
float val = data[i];
|
||||
minDataValue = Mathf.Min(minDataValue, val);
|
||||
maxDataValue = Mathf.Max(maxDataValue, val);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private Texture3D CreateTextureInternal()
|
||||
{
|
||||
TextureFormat texformat = SystemInfo.SupportsTextureFormat(TextureFormat.RHalf) ? TextureFormat.RHalf : TextureFormat.RFloat;
|
||||
Texture3D texture = new Texture3D(dimX, dimY, dimZ, texformat, false);
|
||||
texture.wrapMode = TextureWrapMode.Clamp;
|
||||
|
||||
float minValue = GetMinDataValue();
|
||||
float maxValue = GetMaxDataValue();
|
||||
|
||||
float maxRange = maxValue - minValue;
|
||||
|
||||
Color[] cols = new Color[data.Length]; // data exists
|
||||
for (int x = 0; x < dimX; x++)
|
||||
{
|
||||
for (int y = 0; y < dimY; y++)
|
||||
{
|
||||
for (int z = 0; z < dimZ; z++)
|
||||
{
|
||||
int iData = x + y * dimX + z * (dimX * dimY);
|
||||
cols[iData] = new Color((data[iData] - minValue) / maxRange, 0.0f, 0.0f, 0.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
texture.SetPixels(cols);
|
||||
texture.Apply();
|
||||
return texture;
|
||||
}
|
||||
|
||||
private Texture3D CreateGradientTextureInternal()
|
||||
{
|
||||
TextureFormat texformat = SystemInfo.SupportsTextureFormat(TextureFormat.RGBAHalf) ? TextureFormat.RGBAHalf : TextureFormat.RGBAFloat;
|
||||
Texture3D texture = new Texture3D(dimX, dimY, dimZ, texformat, false);
|
||||
texture.wrapMode = TextureWrapMode.Clamp;
|
||||
|
||||
float minValue = GetMinDataValue();
|
||||
float maxValue = GetMaxDataValue();
|
||||
float maxRange = maxValue - minValue;
|
||||
|
||||
Color[] cols = new Color[data.Length];
|
||||
for (int x = 0; x < dimX; x++)
|
||||
{
|
||||
for (int y = 0; y < dimY; y++)
|
||||
{
|
||||
for (int z = 0; z < dimZ; z++)
|
||||
{
|
||||
int iData = x + y * dimX + z * (dimX * dimY);
|
||||
|
||||
float x1 = data[Math.Min(x + 1, dimX - 1) + y * dimX + z * (dimX * dimY)] - minValue;
|
||||
float x2 = data[Math.Max(x - 1, 0) + y * dimX + z * (dimX * dimY)] - minValue;
|
||||
float y1 = data[x + Math.Min(y + 1, dimY - 1) * dimX + z * (dimX * dimY)] - minValue;
|
||||
float y2 = data[x + Math.Max(y - 1, 0) * dimX + z * (dimX * dimY)] - minValue;
|
||||
float z1 = data[x + y * dimX + Math.Min(z + 1, dimZ - 1) * (dimX * dimY)] - minValue;
|
||||
float z2 = data[x + y * dimX + Math.Max(z - 1, 0) * (dimX * dimY)] - minValue;
|
||||
|
||||
Vector3 grad = new Vector3((x2 - x1) / maxRange, (y2 - y1) / maxRange, (z2 - z1) / maxRange);
|
||||
|
||||
cols[iData] = new Color(grad.x, grad.y, grad.z, (data[iData] - minValue) / maxRange);
|
||||
}
|
||||
}
|
||||
}
|
||||
texture.SetPixels(cols);
|
||||
texture.Apply();
|
||||
return texture;
|
||||
}
|
||||
}
|
||||
}
|
||||
using System;
|
||||
using System.IO;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityVolumeRendering
|
||||
{
|
||||
/// <summary>
|
||||
/// An imported dataset. Has a dimension and a 3D pixel array.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class VolumeDataset : ScriptableObject
|
||||
{
|
||||
public string filePath;
|
||||
|
||||
// Flattened 3D array of data sample values.
|
||||
[SerializeField]
|
||||
public float[] data;
|
||||
|
||||
[SerializeField]
|
||||
public int dimX, dimY, dimZ;
|
||||
|
||||
[SerializeField]
|
||||
public float scaleX = 0.0f, scaleY = 0.0f, scaleZ = 0.0f;
|
||||
public float volumeScale;
|
||||
|
||||
[SerializeField]
|
||||
public string datasetName;
|
||||
|
||||
private float minDataValue = float.MaxValue;
|
||||
private float maxDataValue = float.MinValue;
|
||||
|
||||
private Texture3D dataTexture = null;
|
||||
private Texture3D gradientTexture = null;
|
||||
|
||||
|
||||
public Texture3D GetDataTexture()
|
||||
{
|
||||
dataTexture = CreateTextureInternal();
|
||||
return dataTexture;
|
||||
}
|
||||
|
||||
public Texture3D GetGradientTexture()
|
||||
{
|
||||
gradientTexture = CreateGradientTextureInternal();
|
||||
return gradientTexture;
|
||||
}
|
||||
|
||||
public float GetMinDataValue()
|
||||
{
|
||||
if (minDataValue == float.MaxValue)
|
||||
CalculateValueBounds();
|
||||
return minDataValue;
|
||||
}
|
||||
|
||||
public float GetMaxDataValue()
|
||||
{
|
||||
if (maxDataValue == float.MinValue)
|
||||
CalculateValueBounds();
|
||||
return maxDataValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that the dataset is not too large.
|
||||
/// </summary>
|
||||
public void FixDimensions()
|
||||
{
|
||||
int MAX_DIM = 2048; // 3D texture max size. See: https://docs.unity3d.com/Manual/class-Texture3D.html
|
||||
|
||||
while (Mathf.Max(dimX, dimY, dimZ) > MAX_DIM)
|
||||
{
|
||||
Debug.LogWarning("Dimension exceeds limits (maximum: "+MAX_DIM+"). Dataset is downscaled by 2 on each axis!");
|
||||
DownScaleData();
|
||||
}
|
||||
}
|
||||
|
||||
/// <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 x = 0; x < halfDimX; x++)
|
||||
{
|
||||
for (int y = 0; y < halfDimY; y++)
|
||||
{
|
||||
for (int z = 0; z < halfDimZ; z++)
|
||||
{
|
||||
downScaledData[x + y * halfDimX + z * (halfDimX * halfDimY)] = Mathf.RoundToInt(GetAvgerageVoxelValues(x * 2, y * 2, z * 2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Update data & data dimensions
|
||||
data = downScaledData;
|
||||
dimX = halfDimX;
|
||||
dimY = halfDimY;
|
||||
dimZ = halfDimZ;
|
||||
}
|
||||
|
||||
private void CalculateValueBounds()
|
||||
{
|
||||
minDataValue = float.MaxValue;
|
||||
maxDataValue = float.MinValue;
|
||||
|
||||
for (int i = 0; i < dimX * dimY * dimZ; i++)
|
||||
{
|
||||
if (data != null)
|
||||
{
|
||||
float val = data[i];
|
||||
minDataValue = Mathf.Min(minDataValue, val);
|
||||
maxDataValue = Mathf.Max(maxDataValue, val);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private Texture3D CreateTextureInternal()
|
||||
{
|
||||
TextureFormat texformat = SystemInfo.SupportsTextureFormat(TextureFormat.RHalf) ? TextureFormat.RHalf : TextureFormat.RFloat;
|
||||
Texture3D texture = new Texture3D(dimX, dimY, dimZ, texformat, false);
|
||||
texture.wrapMode = TextureWrapMode.Clamp;
|
||||
|
||||
float minValue = GetMinDataValue();
|
||||
float maxValue = GetMaxDataValue();
|
||||
|
||||
bool isHalfFloat = texformat == TextureFormat.RHalf;
|
||||
try
|
||||
{
|
||||
// Create a byte array for filling the texture. Store has half (16 bit) or single (32 bit) float values.
|
||||
int sampleSize = isHalfFloat ? 2 : 4;
|
||||
byte[] bytes = new byte[data.Length * sampleSize]; // This can cause OutOfMemoryException
|
||||
for (int iData = 0; iData < data.Length; iData++)
|
||||
{
|
||||
float pixelValue = (float)(data[iData] - minValue) / maxRange;
|
||||
byte[] pixelBytes = isHalfFloat ? BitConverter.GetBytes(Mathf.FloatToHalf(pixelValue)) : BitConverter.GetBytes(pixelValue);
|
||||
|
||||
Array.Copy(pixelBytes, 0, bytes, iData * sampleSize, sampleSize);
|
||||
}
|
||||
|
||||
texture.SetPixelData(bytes, 0);
|
||||
}
|
||||
|
||||
catch (OutOfMemoryException ex)
|
||||
{
|
||||
Debug.LogWarning("Out of memory when creating texture. Using fallback method.");
|
||||
for (int x = 0; x < dimX; x++)
|
||||
for (int y = 0; y < dimY; y++)
|
||||
for (int z = 0; z < dimZ; z++)
|
||||
texture.SetPixel(x, y, z, new Color((float)(data[x + y * dimX + z * (dimX * dimY)] - minValue) / maxRange, 0.0f, 0.0f, 0.0f));
|
||||
}
|
||||
texture.Apply();
|
||||
return texture;
|
||||
}
|
||||
|
||||
private Texture3D CreateGradientTextureInternal()
|
||||
{
|
||||
TextureFormat texformat = SystemInfo.SupportsTextureFormat(TextureFormat.RGBAHalf) ? TextureFormat.RGBAHalf : TextureFormat.RGBAFloat;
|
||||
Texture3D texture = new Texture3D(dimX, dimY, dimZ, texformat, false);
|
||||
texture.wrapMode = TextureWrapMode.Clamp;
|
||||
|
||||
float minValue = GetMinDataValue();
|
||||
float maxValue = GetMaxDataValue();
|
||||
float maxRange = maxValue - minValue;
|
||||
|
||||
Color[] cols;
|
||||
try
|
||||
{
|
||||
cols = new Color[data.Length];
|
||||
}
|
||||
catch (OutOfMemoryException ex)
|
||||
{
|
||||
cols = null;
|
||||
}
|
||||
for (int x = 0; x < dimX; x++)
|
||||
{
|
||||
for (int y = 0; y < dimY; y++)
|
||||
{
|
||||
for (int z = 0; z < dimZ; z++)
|
||||
{
|
||||
int iData = x + y * dimX + z * (dimX * dimY);
|
||||
|
||||
float x1 = data[Math.Min(x + 1, dimX - 1) + y * dimX + z * (dimX * dimY)] - minValue;
|
||||
float x2 = data[Math.Max(x - 1, 0) + y * dimX + z * (dimX * dimY)] - minValue;
|
||||
float y1 = data[x + Math.Min(y + 1, dimY - 1) * dimX + z * (dimX * dimY)] - minValue;
|
||||
float y2 = data[x + Math.Max(y - 1, 0) * dimX + z * (dimX * dimY)] - minValue;
|
||||
float z1 = data[x + y * dimX + Math.Min(z + 1, dimZ - 1) * (dimX * dimY)] - minValue;
|
||||
float z2 = data[x + y * dimX + Math.Max(z - 1, 0) * (dimX * dimY)] - minValue;
|
||||
|
||||
Vector3 grad = new Vector3((x2 - x1) / maxRange, (y2 - y1) / maxRange, (z2 - z1) / maxRange);
|
||||
|
||||
if (cols == null)
|
||||
{
|
||||
texture.SetPixel(x, y, z, new Color(grad.x, grad.y, grad.z, (float)(data[iData] - minValue) / maxRange));
|
||||
}
|
||||
else
|
||||
{
|
||||
cols[iData] = new Color(grad.x, grad.y, grad.z, (float)(data[iData] - minValue) / maxRange);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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
|
||||
bool xC = x + 1 == dimX;
|
||||
bool yC = y + 1 == dimY;
|
||||
bool zC = z + 1 == dimZ;
|
||||
|
||||
//if expression can only be true on the edges of the texture
|
||||
if (xC || yC || zC)
|
||||
{
|
||||
if (!xC && yC && zC) return (GetData(x, y, z) + GetData(x + 1, y, z)) / 2.0f;
|
||||
else if (xC && !yC && zC) return (GetData(x, y, z) + GetData(x, y + 1, z)) / 2.0f;
|
||||
else if (xC && yC && !zC) return (GetData(x, y, z) + GetData(x, y, z + 1)) / 2.0f;
|
||||
else if (!xC && !yC && zC) return (GetData(x, y, z) + GetData(x + 1, y, z) + GetData(x, y + 1, z) + GetData(x + 1, y + 1, z)) / 4.0f;
|
||||
else if (!xC && yC && !zC) return (GetData(x, y, z) + GetData(x + 1, y, z) + GetData(x, y, z + 1) + GetData(x + 1, y, z + 1)) / 4.0f;
|
||||
else if (xC && !yC && !zC) return (GetData(x, y, z) + GetData(x, y + 1, z) + GetData(x, y, z + 1) + GetData(x, y + 1, z + 1)) / 4.0f;
|
||||
else return GetData(x, y, z); // if xC && yC && zC
|
||||
}
|
||||
return (GetData(x, y, z) + GetData(x + 1, y, z) + GetData(x, y + 1, z) + GetData(x + 1, y + 1, z)
|
||||
+ GetData(x, y, z + 1) + GetData(x, y + 1, z + 1) + GetData(x + 1, y, z + 1) + GetData(x + 1, y + 1, z + 1)) / 8.0f;
|
||||
}
|
||||
|
||||
public int GetData(int x, int y, int z)
|
||||
{
|
||||
return data[x + y * dimX + z * (dimX * dimY)];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: b11b1f848245f974ab893c35ca3aae92
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Загрузка…
Ссылка в новой задаче