Merge pull request #59 from jasonks2/PARCHG---Electron-Density-Volume-Renderer
VASP support + store dataset values as float instead of int.
|
@ -1,8 +1,9 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityVolumeRendering
|
||||
{
|
||||
|
@ -11,7 +12,6 @@ namespace UnityVolumeRendering
|
|||
public static void ImportDataset(string filePath)
|
||||
{
|
||||
DatasetType datasetType = DatasetImporterUtility.GetDatasetType(filePath);
|
||||
|
||||
switch (datasetType)
|
||||
{
|
||||
case DatasetType.Raw:
|
||||
|
@ -50,6 +50,21 @@ namespace UnityVolumeRendering
|
|||
}
|
||||
break;
|
||||
}
|
||||
case DatasetType.PARCHG:
|
||||
{
|
||||
ParDatasetImporter importer = new ParDatasetImporter(filePath);
|
||||
VolumeDataset dataset = importer.Import();
|
||||
|
||||
if (dataset != null)
|
||||
{
|
||||
VolumeRenderedObject obj = VolumeObjectFactory.CreateObject(dataset);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("Failed to import datset");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
using UnityEditor;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
|
@ -23,6 +23,20 @@ namespace UnityVolumeRendering
|
|||
}
|
||||
}
|
||||
|
||||
[MenuItem("Volume Rendering/Load dataset/Load PARCHG dataset")]
|
||||
static void ShowParDatasetImporter()
|
||||
{
|
||||
string file = EditorUtility.OpenFilePanel("Select a dataset to load", "DataFiles", "");
|
||||
if (File.Exists(file))
|
||||
{
|
||||
EditorDatasetImporter.ImportDataset(file);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("File doesn't exist: " + file);
|
||||
}
|
||||
}
|
||||
|
||||
[MenuItem("Volume Rendering/Load dataset/Load DICOM")]
|
||||
static void ShowDICOMImporter()
|
||||
{
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
@ -17,11 +17,18 @@ namespace UnityVolumeRendering
|
|||
{
|
||||
GUILayout.BeginVertical();
|
||||
|
||||
|
||||
// Show dataset import buttons
|
||||
if(GUILayout.Button("Import RAW dataset"))
|
||||
{
|
||||
RuntimeFileBrowser.ShowOpenFileDialog(OnOpenRAWDatasetResult, "DataFiles");
|
||||
}
|
||||
|
||||
if(GUILayout.Button("Import PARCHG dataset"))
|
||||
{
|
||||
RuntimeFileBrowser.ShowOpenFileDialog(OnOpenPARDatasetResult, "DataFiles");
|
||||
}
|
||||
|
||||
if (GUILayout.Button("Import DICOM dataset"))
|
||||
{
|
||||
RuntimeFileBrowser.ShowOpenDirectoryDialog(OnOpenDICOMDatasetResult);
|
||||
|
@ -42,10 +49,26 @@ namespace UnityVolumeRendering
|
|||
GUILayout.EndVertical();
|
||||
}
|
||||
|
||||
private void OnOpenPARDatasetResult(RuntimeFileBrowser.DialogResult result)
|
||||
{
|
||||
if (!result.cancelled)
|
||||
{
|
||||
DespawnAllDatasets();
|
||||
string filePath = result.path;
|
||||
ParDatasetImporter parimporter = new ParDatasetImporter(filePath);
|
||||
VolumeDataset dataset = parimporter.Import(); //overriden somewhere
|
||||
if (dataset != null)
|
||||
{
|
||||
VolumeObjectFactory.CreateObject(dataset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnOpenRAWDatasetResult(RuntimeFileBrowser.DialogResult result)
|
||||
{
|
||||
if(!result.cancelled)
|
||||
{
|
||||
|
||||
// We'll only allow one dataset at a time in the runtime GUI (for simplicity)
|
||||
DespawnAllDatasets();
|
||||
|
||||
|
|
|
@ -133,7 +133,7 @@ namespace UnityVolumeRendering
|
|||
dataset.dimZ = files.Count;
|
||||
|
||||
int dimension = dataset.dimX * dataset.dimY * dataset.dimZ;
|
||||
dataset.data = new int[dimension];
|
||||
dataset.data = new float[dimension];
|
||||
|
||||
for (int iSlice = 0; iSlice < files.Count; iSlice++)
|
||||
{
|
||||
|
@ -153,7 +153,7 @@ namespace UnityVolumeRendering
|
|||
int pixelValue = pixelArr[pixelIndex];
|
||||
float hounsfieldValue = pixelValue * slice.slope + slice.intercept;
|
||||
|
||||
dataset.data[dataIndex] = (int)Mathf.Clamp(hounsfieldValue, -1024.0f, 3071.0f);
|
||||
dataset.data[dataIndex] = Mathf.Clamp(hounsfieldValue, -1024.0f, 3071.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
|
||||
using System.IO;
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using UnityEditor;
|
||||
|
||||
namespace UnityVolumeRendering
|
||||
{
|
||||
|
@ -7,7 +9,8 @@ namespace UnityVolumeRendering
|
|||
{
|
||||
Unknown,
|
||||
Raw,
|
||||
DICOM
|
||||
DICOM,
|
||||
PARCHG
|
||||
}
|
||||
|
||||
public class DatasetImporterUtility
|
||||
|
@ -19,18 +22,30 @@ namespace UnityVolumeRendering
|
|||
|
||||
// Check file extension
|
||||
string extension = Path.GetExtension(filePath);
|
||||
if (extension == ".dat" || extension == ".raw" || extension == ".vol")
|
||||
|
||||
if (String.Equals(extension, ".vasp"))
|
||||
{
|
||||
datasetType = DatasetType.PARCHG;
|
||||
}
|
||||
|
||||
else if (extension == ".dat" || extension == ".raw" || extension == ".vol")
|
||||
datasetType = DatasetType.Raw;
|
||||
|
||||
else if (extension == ".ini")
|
||||
{
|
||||
filePath = filePath.Substring(0, filePath.LastIndexOf("."));
|
||||
datasetType = DatasetType.Raw;
|
||||
}
|
||||
else if (extension == ".dicom" || extension == ".dcm")
|
||||
{
|
||||
datasetType = DatasetType.DICOM;
|
||||
else
|
||||
datasetType = DatasetType.Unknown;
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
datasetType = DatasetType.Unknown;
|
||||
}
|
||||
|
||||
return datasetType;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -140,7 +140,7 @@ namespace UnityVolumeRendering
|
|||
{
|
||||
name = name,
|
||||
datasetName = name,
|
||||
data = data,
|
||||
data = Array.ConvertAll(data, new Converter<int, float>((int val) => { return Convert.ToSingle(val); })),
|
||||
dimX = dimensions.x,
|
||||
dimY = dimensions.y,
|
||||
dimZ = dimensions.z,
|
||||
|
|
|
@ -0,0 +1,353 @@
|
|||
/*----------------------------------------------------------------------------
|
||||
# file made by Jason (jasonks2)
|
||||
# project start 8-10-2021
|
||||
# finished 9-7-2021
|
||||
#
|
||||
# Thank you to prof. Andre Schleife
|
||||
# Thank you to dano - "chg2cube.pl" (perl vasp library)
|
||||
# Thank you to mlavik - Unity Volume Rendering
|
||||
# Thank you to Sung Sakong, Dept. of Phys., Univsity Duisburg-Essen
|
||||
#
|
||||
# RCS INFORMATION:
|
||||
# $RCSfile: vaspparchgplugin.c,v $
|
||||
# $Author: johns $
|
||||
------------------------------------------------------------------------------*/
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityVolumeRendering
|
||||
{
|
||||
public class ParDatasetImporter
|
||||
{
|
||||
string filePath;
|
||||
string fileName;
|
||||
float latticeConstant;
|
||||
|
||||
string[] atomNames;
|
||||
int[] atomCount;
|
||||
int totalAtomCount;
|
||||
|
||||
float[][] basisCells;
|
||||
float[][] coordinatebasisCells;
|
||||
float[][] cartesiancoordinatebasisCells;
|
||||
bool isDirect;
|
||||
|
||||
int nx;
|
||||
int ny;
|
||||
int nz;
|
||||
int gridDataLines;
|
||||
float volume;
|
||||
float volumeScale;
|
||||
|
||||
float[] dataGrid;
|
||||
string[] densityLine;
|
||||
|
||||
int[] dimArray;
|
||||
int dimTotal;
|
||||
string[] densityTrim;
|
||||
float[] volumeScaledData;
|
||||
|
||||
string[] fileContentLines;
|
||||
int fileContentIndex;
|
||||
|
||||
public ParDatasetImporter(string filePath)
|
||||
{
|
||||
this.filePath = filePath;
|
||||
}
|
||||
|
||||
public VolumeDataset Import() //fills VolumeDataset object
|
||||
{
|
||||
var extension = Path.GetExtension(filePath);
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
Debug.LogError("The file does not exist: " + filePath);
|
||||
return null;
|
||||
}
|
||||
|
||||
fileContentLines = File.ReadLines(filePath).Where(x => x.Trim(' ') != "").ToArray();
|
||||
fileContentIndex = 0;
|
||||
|
||||
ReadSystemTitle();
|
||||
ReadLatticeConstant();
|
||||
ReadLatticeVectors();
|
||||
GetVolume();
|
||||
ReadAtomNames();
|
||||
ReadAtomSum();
|
||||
ReadCoordinateSystemType();
|
||||
ReadCoordinates();
|
||||
if (isDirect)
|
||||
{
|
||||
cartesiancoordinatebasisCells = ToCartesian();
|
||||
}
|
||||
|
||||
ReadDimensions();
|
||||
dimTotal = dimArray[0] * dimArray[1] * dimArray[2];
|
||||
nx = dimArray[0];
|
||||
ny = dimArray[1];
|
||||
nz = dimArray[2]; // dimensions
|
||||
|
||||
CalculateDataLines();
|
||||
ReadGrid();
|
||||
|
||||
VolumeDataset dataFiller = new VolumeDataset(); //volume object then gets sent to VolumeObjectFactory
|
||||
dataFiller.datasetName = fileName;
|
||||
dataFiller.filePath = filePath;
|
||||
dataFiller.dimX = nx;
|
||||
dataFiller.dimY = ny;
|
||||
dataFiller.dimZ = nz;
|
||||
dataFiller.volumeScale = (float)(1 / volumeScale);
|
||||
dataFiller.data = new float[dimTotal];
|
||||
volumeScaledData = new float[dimTotal];
|
||||
|
||||
for (int ix = 0; ix < nx; ix++)
|
||||
{
|
||||
for (int iy = 0; iy < ny; iy++)
|
||||
{
|
||||
for (int iz = 0; iz < nz; iz++)
|
||||
{
|
||||
int itr = (iz * nx * ny) + (iy * nx) + ix;
|
||||
volumeScaledData[itr] = (float)dataGrid[itr] * (float)dataFiller.volumeScale * (float)0.036749309; //density * volumescale * e_units
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < dimTotal; i++)
|
||||
{
|
||||
dataFiller.data[i] = dataGrid[i];
|
||||
}
|
||||
|
||||
Debug.Log("Loaded dataset in range: " + dataFiller.GetMinDataValue() + " - " + dataFiller.GetMaxDataValue());
|
||||
|
||||
return dataFiller;
|
||||
}
|
||||
|
||||
private string ParseLine()
|
||||
{
|
||||
Debug.Assert(fileContentIndex < fileContentLines.Length);
|
||||
return fileContentLines[fileContentIndex++];
|
||||
}
|
||||
|
||||
private string PeekLine()
|
||||
{
|
||||
Debug.Assert(fileContentIndex < fileContentLines.Length);
|
||||
return fileContentLines[fileContentIndex];
|
||||
}
|
||||
|
||||
public void ReadSystemTitle()
|
||||
{
|
||||
ParseLine(); // We don't use header comment for anything now
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads lattice parameter
|
||||
/// </summary>
|
||||
public void ReadLatticeConstant()
|
||||
{
|
||||
var line = ParseLine();
|
||||
string[] bits = line.Trim().Split(' ').Where(x => x != "").ToArray();
|
||||
|
||||
latticeConstant = float.Parse(bits[0]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Multiplies basis by lattice parameter
|
||||
/// </summary>
|
||||
public void ReadLatticeVectors()
|
||||
{
|
||||
basisCells = new float[3][];
|
||||
basisCells[0] = new float[3];
|
||||
basisCells[1] = new float[3];
|
||||
basisCells[2] = new float[3];
|
||||
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
string latticeLine = ParseLine();
|
||||
string[] vectorString = latticeLine.Trim().Split(' ').Where(t => t.Length > 0).ToArray();
|
||||
Debug.Assert(vectorString.Length == 3);
|
||||
|
||||
basisCells[i][0] = float.Parse(vectorString[0]) * latticeConstant;
|
||||
basisCells[i][1] = float.Parse(vectorString[1]) * latticeConstant;
|
||||
basisCells[i][2] = float.Parse(vectorString[2]) * latticeConstant;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Density is written in as p * V we must divide the volume (same as multiplying the scale)
|
||||
/// </summary>
|
||||
public void GetVolume()
|
||||
{
|
||||
volume = basisCells[0][0] * (basisCells[1][1] * basisCells[2][2] - basisCells[2][1] * basisCells[1][2])
|
||||
- basisCells[1][0] * (basisCells[0][1] * basisCells[2][2] - basisCells[2][1] * basisCells[0][2])
|
||||
+ basisCells[2][0] * (basisCells[0][1] * basisCells[1][2] - basisCells[1][1] * basisCells[0][2]);
|
||||
Debug.Log(volume);
|
||||
// make sure volume is +
|
||||
// this volume is in units selected (default bohr) but we need it in ang**3
|
||||
volumeScale = Math.Abs(volume) / ((float)Math.Pow(1.889725992, 3)); //bohr/hartree -> ang/eV
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads molecule works for as many atoms
|
||||
/// </summary>
|
||||
public void ReadAtomNames()
|
||||
{
|
||||
var line = PeekLine();
|
||||
string[] names = line.Trim().Split(' ').Where(t => t.Length > 0).ToArray();
|
||||
int num = 0;
|
||||
if (int.TryParse(names[0], out num))
|
||||
return; // Current line is atom count (no atom names specified in this file)
|
||||
else
|
||||
{
|
||||
ParseLine(); // Increment line index
|
||||
atomNames = names;
|
||||
}
|
||||
}
|
||||
|
||||
public void ReadAtomSum()
|
||||
{
|
||||
var line = ParseLine();
|
||||
string[] atomCountStrings = line.Trim().Split(' ').Where(t => t.Length > 0).ToArray();
|
||||
atomCount = new int[atomCountStrings.Length];
|
||||
for (int i = 0; i < atomCountStrings.Length; i++)
|
||||
atomCount[i] = Int16.Parse(atomCountStrings[i]);
|
||||
|
||||
totalAtomCount = atomCount.Sum();
|
||||
}
|
||||
|
||||
public void ReadCoordinates()
|
||||
{
|
||||
coordinatebasisCells = new float[totalAtomCount][];
|
||||
|
||||
string latticeLine = null;
|
||||
|
||||
// unspecified m-array size initializer for loop M given N = 3
|
||||
for (int x = 0; x < totalAtomCount; x++)
|
||||
{
|
||||
coordinatebasisCells[x] = new float[3]; //3 for x y z
|
||||
}
|
||||
for (int i = 0; i < totalAtomCount; i++)
|
||||
{
|
||||
latticeLine = ParseLine();
|
||||
string[] vectorString = latticeLine.Trim().Split(' ').Where(x => x != "").ToArray();
|
||||
coordinatebasisCells[i][0] = float.Parse(vectorString[0]);
|
||||
coordinatebasisCells[i][1] = float.Parse(vectorString[1]);
|
||||
coordinatebasisCells[i][2] = float.Parse(vectorString[2]);
|
||||
}
|
||||
}
|
||||
|
||||
// Reads whether system is in Direct or Cartesian
|
||||
public void ReadCoordinateSystemType()
|
||||
{
|
||||
string molecule = null;
|
||||
string compare = "Direct";
|
||||
|
||||
var line = ParseLine();
|
||||
string cardinality = line.Trim();
|
||||
|
||||
isDirect = string.Equals(cardinality, compare);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Direct mode -> Cartesian.
|
||||
/// TODO: took one day to debug b/c the sum was subject to change via function call getatomcount() SOMEONE FIX
|
||||
/// </summary>
|
||||
public float[][] ToCartesian()
|
||||
{
|
||||
float[][] cartesiancoordinatebasisCells = new float[totalAtomCount][];
|
||||
|
||||
//initialize memory
|
||||
for (int x = 0; x < totalAtomCount; x++)
|
||||
{
|
||||
cartesiancoordinatebasisCells[x] = new float[3];
|
||||
}
|
||||
float[][] coordinatebasisCells = new float[totalAtomCount][];
|
||||
for (int x = 0; x < totalAtomCount; x++)
|
||||
{
|
||||
coordinatebasisCells[x] = new float[3];
|
||||
}
|
||||
|
||||
if (!isDirect)
|
||||
{
|
||||
Debug.Log("Input atomic position array is already Cartesian");
|
||||
}
|
||||
else
|
||||
{
|
||||
//conversion here
|
||||
for (int i = 0; i < totalAtomCount; i++)
|
||||
{
|
||||
float v1 = coordinatebasisCells[i][0] * basisCells[0][0] + coordinatebasisCells[i][1] * basisCells[0][1] + coordinatebasisCells[i][2] * basisCells[0][2];
|
||||
float v2 = coordinatebasisCells[i][0] * basisCells[1][0] + coordinatebasisCells[i][1] * basisCells[1][1] + coordinatebasisCells[i][2] * basisCells[1][2];
|
||||
float v3 = coordinatebasisCells[i][0] * basisCells[2][0] + coordinatebasisCells[i][1] * basisCells[2][1] + coordinatebasisCells[i][2] * basisCells[2][2];
|
||||
cartesiancoordinatebasisCells[i][0] = v1;
|
||||
cartesiancoordinatebasisCells[i][1] = v2;
|
||||
cartesiancoordinatebasisCells[i][2] = v3;
|
||||
}
|
||||
}
|
||||
|
||||
return cartesiancoordinatebasisCells;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates nx * ny * nz
|
||||
/// </summary>
|
||||
public void ReadDimensions()
|
||||
{
|
||||
var line = ParseLine();
|
||||
string grid = line.Trim();
|
||||
|
||||
dimArray = new int[3]; //size of atom types (Cd Se) -> 2
|
||||
|
||||
// Split on one or more non-digit characters.
|
||||
string[] numbers = Regex.Split(grid, @"\D+");
|
||||
for (int q = 0; q < numbers.Count(); q++)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(numbers[q]))
|
||||
{
|
||||
dimArray[q] = int.Parse(numbers[q]);
|
||||
}
|
||||
}
|
||||
|
||||
for (int m = 0; m < 3; m++)
|
||||
{
|
||||
nx = dimArray[0];
|
||||
ny = dimArray[1];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Each line contains 10 elements, therefore there are 10 colums. Divide by 10
|
||||
/// </summary>
|
||||
public void CalculateDataLines()
|
||||
{
|
||||
gridDataLines = dimTotal / 10;
|
||||
}
|
||||
|
||||
public void ReadGrid()
|
||||
{
|
||||
dataGrid = new float[dimTotal];
|
||||
List<float> data = new List<float>();
|
||||
|
||||
for (int i = 0; i < gridDataLines + 1; i++)
|
||||
{
|
||||
if (fileContentIndex == fileContentLines.Length)
|
||||
break; // TODO: Find a more elegant solution (some datasets will have one extra line -> see the "+ 1" in loop above.)
|
||||
|
||||
string gridRow = ParseLine().Trim();
|
||||
|
||||
densityLine = Regex.Split(gridRow, @"(/^[+\-]?(?=.)(0|[1-9]\d*)?(\.\d*)?(?:(\d)[eE][+\-]?\d+)?$/)"); //thank stackoverflow
|
||||
densityTrim = densityLine[0].Trim().Split(' ');
|
||||
|
||||
for (int r = 0; r < densityTrim.Length; r++)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(densityTrim[r]) && (Regex.IsMatch(densityTrim[r], @"\d")) && !string.IsNullOrWhiteSpace(densityTrim[r]))
|
||||
{
|
||||
data.Add(float.Parse(densityTrim[r]));
|
||||
}
|
||||
}
|
||||
}
|
||||
dataGrid = data.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 4b0a1ee15267941c9bae1119500df091
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.IO;
|
||||
using UnityEngine;
|
||||
|
||||
|
@ -64,6 +64,7 @@ namespace UnityVolumeRendering
|
|||
|
||||
VolumeDataset dataset = new VolumeDataset();
|
||||
dataset.datasetName = Path.GetFileName(filePath);
|
||||
dataset.filePath = filePath;
|
||||
dataset.dimX = dimX;
|
||||
dataset.dimY = dimY;
|
||||
dataset.dimZ = dimZ;
|
||||
|
@ -73,12 +74,12 @@ namespace UnityVolumeRendering
|
|||
reader.ReadBytes(skipBytes);
|
||||
|
||||
int uDimension = dimX * dimY * dimZ;
|
||||
dataset.data = new int[uDimension];
|
||||
dataset.data = new float[uDimension];
|
||||
|
||||
// Read the data/sample values
|
||||
for (int i = 0; i < uDimension; i++)
|
||||
{
|
||||
dataset.data[i] = ReadDataValue(reader);
|
||||
dataset.data[i] = (float)ReadDataValue(reader);
|
||||
}
|
||||
Debug.Log("Loaded dataset in range: " + dataset.GetMinDataValue() + " - " + dataset.GetMaxDataValue());
|
||||
|
||||
|
@ -178,4 +179,4 @@ namespace UnityVolumeRendering
|
|||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
using UnityEngine;
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
@ -81,4 +81,4 @@ namespace UnityVolumeRendering
|
|||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,169 +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)
|
||||
{
|
||||
int minValue = dataset.GetMinDataValue();
|
||||
int maxValue = dataset.GetMaxDataValue();
|
||||
int numValues = maxValue - minValue + 1;
|
||||
|
||||
float valRangeRecip = 1.0f / (maxValue - minValue);
|
||||
|
||||
int numSamples = System.Math.Min(numValues, 1024);
|
||||
int[] values = new int[numSamples];
|
||||
Color[] cols = new Color[numSamples];
|
||||
Texture2D texture = new Texture2D(numSamples, 1, TextureFormat.RGBAFloat, false);
|
||||
|
||||
int maxFreq = 0;
|
||||
for (int iData = 0; iData < dataset.data.Length; iData++)
|
||||
{
|
||||
int dataValue = dataset.data[iData];
|
||||
float tValue = (dataValue - minValue) * valRangeRecip;
|
||||
int valueIndex = Mathf.RoundToInt((numSamples - 1) * tValue);
|
||||
values[valueIndex] += 1;
|
||||
maxFreq = System.Math.Max(values[valueIndex], maxFreq);
|
||||
}
|
||||
|
||||
for (int iSample = 0; iSample < numSamples; iSample++)
|
||||
cols[iSample] = new Color(Mathf.Log10((float)values[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)
|
||||
{
|
||||
int numValues = dataset.GetMaxDataValue() - dataset.GetMinDataValue() + 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);
|
||||
|
||||
int 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 = dataset.data[iData];
|
||||
|
||||
int x1 = dataset.data[(x + 1) + y * dataset.dimX + z * (dataset.dimX * dataset.dimY)];
|
||||
int x2 = dataset.data[(x - 1) + y * dataset.dimX + z * (dataset.dimX * dataset.dimY)];
|
||||
int y1 = dataset.data[x + (y + 1) * dataset.dimX + z * (dataset.dimX * dataset.dimY)];
|
||||
int y2 = dataset.data[x + (y - 1) * dataset.dimX + z * (dataset.dimX * dataset.dimY)];
|
||||
int z1 = dataset.data[x + y * dataset.dimX + (z + 1) * (dataset.dimX * dataset.dimY)];
|
||||
int 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
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)
|
||||
{
|
||||
float minValue = dataset.GetMinDataValue();
|
||||
float maxValue = dataset.GetMaxDataValue();
|
||||
|
||||
// Value range of the density values.
|
||||
float densityValRange = maxValue - minValue + 1.0f;
|
||||
float densityRangeRecip = 1.0f / (maxValue - minValue); // reciprocal
|
||||
// Clamp density value samples.
|
||||
int numDensitySamples = System.Math.Min((int)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,236 +1,239 @@
|
|||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityVolumeRendering
|
||||
{
|
||||
/// <summary>
|
||||
/// An imported dataset. Has a dimension and a 3D pixel array.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class VolumeDataset : ScriptableObject
|
||||
{
|
||||
// Flattened 3D array of data sample values.
|
||||
[SerializeField]
|
||||
public int[] data = null;
|
||||
|
||||
[SerializeField]
|
||||
public int dimX, dimY, dimZ;
|
||||
|
||||
[SerializeField]
|
||||
public float scaleX = 0.0f, scaleY = 0.0f, scaleZ = 0.0f;
|
||||
|
||||
[SerializeField]
|
||||
public string datasetName;
|
||||
|
||||
private int minDataValue = int.MaxValue;
|
||||
private int maxDataValue = int.MinValue;
|
||||
private Texture3D dataTexture = null;
|
||||
private Texture3D gradientTexture = null;
|
||||
|
||||
public Texture3D GetDataTexture()
|
||||
{
|
||||
if (dataTexture == null)
|
||||
{
|
||||
dataTexture = CreateTextureInternal();
|
||||
}
|
||||
return dataTexture;
|
||||
}
|
||||
|
||||
public Texture3D GetGradientTexture()
|
||||
{
|
||||
if (gradientTexture == null)
|
||||
{
|
||||
gradientTexture = CreateGradientTextureInternal();
|
||||
}
|
||||
return gradientTexture;
|
||||
}
|
||||
|
||||
public int GetMinDataValue()
|
||||
{
|
||||
if (minDataValue == int.MaxValue)
|
||||
CalculateValueBounds();
|
||||
return minDataValue;
|
||||
}
|
||||
|
||||
public int GetMaxDataValue()
|
||||
{
|
||||
if (maxDataValue == int.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 = int.MaxValue;
|
||||
maxDataValue = int.MinValue;
|
||||
int dim = dimX * dimY * dimZ;
|
||||
for (int i = 0; i < dim; i++)
|
||||
{
|
||||
int val = data[i];
|
||||
minDataValue = Math.Min(minDataValue, val);
|
||||
maxDataValue = Math.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;
|
||||
|
||||
int minValue = GetMinDataValue();
|
||||
int maxValue = GetMaxDataValue();
|
||||
int maxRange = maxValue - minValue;
|
||||
|
||||
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;
|
||||
|
||||
int minValue = GetMinDataValue();
|
||||
int maxValue = GetMaxDataValue();
|
||||
int 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);
|
||||
|
||||
int x1 = data[Math.Min(x + 1, dimX - 1) + y * dimX + z * (dimX * dimY)] - minValue;
|
||||
int x2 = data[Math.Max(x - 1, 0) + y * dimX + z * (dimX * dimY)] - minValue;
|
||||
int y1 = data[x + Math.Min(y + 1, dimY - 1) * dimX + z * (dimX * dimY)] - minValue;
|
||||
int y2 = data[x + Math.Max(y - 1, 0) * dimX + z * (dimX * dimY)] - minValue;
|
||||
int z1 = data[x + y * dimX + Math.Min(z + 1, dimZ - 1) * (dimX * dimY)] - minValue;
|
||||
int z2 = data[x + y * dimX + Math.Max(z - 1, 0) * (dimX * dimY)] - minValue;
|
||||
|
||||
Vector3 grad = new Vector3((x2 - x1) / (float)maxRange, (y2 - y1) / (float)maxRange, (z2 - z1) / (float)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)];
|
||||
}
|
||||
}
|
||||
}
|
||||
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;
|
||||
float[] downScaledData = new float[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.Round(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;
|
||||
|
||||
if (data != null)
|
||||
{
|
||||
for (int i = 0; i < dimX * dimY * dimZ; i++)
|
||||
{
|
||||
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;
|
||||
|
||||
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 float GetData(int x, int y, int z)
|
||||
{
|
||||
return data[x + y * dimX + z * (dimX * dimY)];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,5 +5,6 @@ If you run into any issues, don't hesitate to report it to me (through the "Issu
|
|||
- Create a fork (click the "Fork"-button in the upper right corner)
|
||||
- Create a feature branch (`git checkout -b branch_name`)
|
||||
- Implement your changes
|
||||
- (optionally) add your name to [CREDITS.md](CREDITS.md)
|
||||
- Push
|
||||
- Create a pull request from the fork: https://help.github.com/en/articles/creating-a-pull-request-from-a-fork
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
Contributors:
|
||||
|
||||
- [Matias Lavik](https://github.com/mlavik1)
|
||||
Original project.
|
||||
- [jasonks2](https://github.com/jasonks2):
|
||||
Implemented support for PARCHG (.vasp) datasets.
|
||||
- [denistribouillois](https://github.com/denistribouillois)
|
||||
Slicing plane improvements. Histogram GPU calculation.
|
||||
- [Michael Ovens](https://github.com/MichaelOvens)
|
||||
Image sequence import. Other contributions.
|
||||
- [Chiara Di Vece](https://github.com/chiaradivece)
|
||||
GUI for modifying slicing plane positiomn/orientation.
|
||||
- [btsai-dev](https://github.com/btsai-dev)
|
||||
Memory leak fix
|
||||
|
||||
Feel free to add yourself to this list when contributing to this project.
|
|
@ -0,0 +1 @@
|
|||
This dataset has no license associated with it, but permission to use it has been granted by University of Illinois, Urbana-Champaign. Reference person: https://github.com/jasonks2
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"com.unity.modules.imageconversion": {
|
||||
"version": "1.0.0",
|
||||
"depth": 0,
|
||||
"source": "builtin",
|
||||
"dependencies": {}
|
||||
},
|
||||
"com.unity.modules.imgui": {
|
||||
"version": "1.0.0",
|
||||
"depth": 0,
|
||||
"source": "builtin",
|
||||
"dependencies": {}
|
||||
},
|
||||
"com.unity.modules.jsonserialize": {
|
||||
"version": "1.0.0",
|
||||
"depth": 0,
|
||||
"source": "builtin",
|
||||
"dependencies": {}
|
||||
}
|
||||
}
|
||||
}
|
20
README.md
|
@ -106,17 +106,23 @@ The importer takes the following parameters:
|
|||
- skipBytes: Number of bytes to skip (offset to where the data begins). This is usually the same as the header size, and will be 0 if there is no header.
|
||||
|
||||
All this info can be added to a ".ini"-file, which the importer will use (if it finds any). See the sample files (in the "DataFiles" folder for an example).
|
||||
|
||||
# Todo:
|
||||
- Improve 2D Transfer Function editor: Better GUI, more shapes (triangles)
|
||||
- Optimise histogram generation
|
||||
- Support very large datasets (currently we naively try to create 3D textures with the same dimension as the data)
|
||||
|
||||
![alt tag](https://github.com/mlavik1/UnityVolumeRendering/blob/master/Screenshots/slices.gif)
|
||||
![alt tag](https://github.com/mlavik1/UnityVolumeRendering/blob/master/Screenshots/1.png)
|
||||
![alt tag](https://github.com/mlavik1/UnityVolumeRendering/blob/master/Screenshots/2.png)
|
||||
![alt tag](https://github.com/mlavik1/UnityVolumeRendering/blob/master/Screenshots/3.png)
|
||||
![alt tag](https://github.com/mlavik1/UnityVolumeRendering/blob/master/Screenshots/4.png)
|
||||
![alt tag](https://github.com/mlavik1/UnityVolumeRendering/blob/master/Screenshots/5.png)
|
||||
![alt tag](https://github.com/mlavik1/UnityVolumeRendering/blob/master/Screenshots/6.png)
|
||||
![alt tag](Screenshots/slices.gif)
|
||||
![alt tag](Screenshots/1.png)
|
||||
![alt tag](Screenshots/2.png)
|
||||
![alt tag](Screenshots/4.png)
|
||||
![alt tag](Screenshots/5.png)
|
||||
![alt tag](Screenshots/6.png)
|
||||
![alt tag](Screenshots/regions.png)
|
||||
|
||||
# Contributing
|
||||
See [CONTRIBUTING.md](CONTRIBUTING.md) for how to contribute.
|
||||
|
||||
Thanks to [everyone who have contributed so far](CREDITS.md).
|
||||
|
||||
See ACKNOWLEDGEMENTS.txt for libraries used by this project.
|
||||
|
|
После Ширина: | Высота: | Размер: 240 KiB |
После Ширина: | Высота: | Размер: 562 KiB |
После Ширина: | Высота: | Размер: 331 KiB |
После Ширина: | Высота: | Размер: 775 KiB |
После Ширина: | Высота: | Размер: 591 KiB |
После Ширина: | Высота: | Размер: 396 KiB |
После Ширина: | Высота: | Размер: 1.0 MiB |
После Ширина: | Высота: | Размер: 512 KiB |