Skip to content

Commit 1d4fc24

Browse files
committed
image sequence importer with density helper class & density source enum
1 parent 6a43f45 commit 1d4fc24

File tree

7 files changed

+317
-0
lines changed

7 files changed

+317
-0
lines changed

Assets/Editor/VolumeRendererEditorFunctions.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,22 @@ static void ShowDICOMImporter()
3636
Debug.LogError("Directory doesn't exist: " + dir);
3737
}
3838
}
39+
40+
[MenuItem("Volume Rendering/Load image sequence")]
41+
static void ShowSequenceImporter()
42+
{
43+
string dir = EditorUtility.OpenFolderPanel("Select a folder to load", "", "");
44+
if (Directory.Exists(dir))
45+
{
46+
ImageSequenceImporter importer = new ImageSequenceImporter(dir);
47+
VolumeDataset dataset = importer.Import();
48+
if (dataset != null)
49+
VolumeObjectFactory.CreateObject(dataset);
50+
}
51+
else
52+
{
53+
Debug.LogError("Directory doesn't exist: " + dir);
54+
}
55+
}
3956
}
4057
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
using System;
2+
using UnityEngine;
3+
4+
namespace UnityVolumeRendering
5+
{
6+
public static class DensityHelper
7+
{
8+
public static DensitySource IdentifyDensitySource(Color[] voxels)
9+
{
10+
DensitySource source = DensitySource.Unknown;
11+
12+
for (int i = 0; i < voxels.Length - 1; i++)
13+
{
14+
if (!Mathf.Approximately(voxels[i].a, voxels[i + 1].a))
15+
{
16+
source = DensitySource.Alpha;
17+
break;
18+
}
19+
else if (!Mathf.Approximately(voxels[i].r, voxels[i + 1].r))
20+
{
21+
source = DensitySource.Grey;
22+
break;
23+
}
24+
else if (!Mathf.Approximately(voxels[i].g, voxels[i + 1].g))
25+
{
26+
source = DensitySource.Grey;
27+
break;
28+
}
29+
else if (!Mathf.Approximately(voxels[i].b, voxels[i + 1].b))
30+
{
31+
source = DensitySource.Grey;
32+
break;
33+
}
34+
}
35+
36+
return source;
37+
}
38+
39+
public static int[] ConvertColorsToDensities (Color[] colors)
40+
{
41+
DensitySource source = IdentifyDensitySource(colors);
42+
return ConvertColorsToDensities(colors, source);
43+
}
44+
45+
public static int[] ConvertColorsToDensities (Color[] colors, DensitySource source)
46+
{
47+
int[] densities = new int[colors.Length];
48+
for (int i = 0; i < densities.Length; i++)
49+
densities[i] = ConvertColorToDensity(colors[i], source);
50+
return densities;
51+
}
52+
53+
public static int ConvertColorToDensity (Color color, DensitySource source)
54+
{
55+
switch (source)
56+
{
57+
case DensitySource.Alpha:
58+
return Mathf.RoundToInt(color.a * 255f);
59+
case DensitySource.Grey:
60+
return Mathf.RoundToInt(color.r * 255f);
61+
default:
62+
throw new ArgumentOutOfRangeException(source.ToString());
63+
}
64+
}
65+
66+
public static Color ConvertDensityToColor (int density, DensitySource source)
67+
{
68+
float grey = source == DensitySource.Grey ? density / 255f : 0f;
69+
float alpha = source == DensitySource.Alpha ? density / 255f : 0f;
70+
71+
return new Color()
72+
{
73+
r = grey,
74+
g = grey,
75+
b = grey,
76+
a = alpha
77+
};
78+
}
79+
}
80+
}

Assets/Scripts/Importing/DensityHelper.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
namespace UnityVolumeRendering
2+
{
3+
public enum DensitySource
4+
{
5+
Unknown,
6+
Alpha,
7+
Grey
8+
}
9+
}

Assets/Scripts/Importing/DensitySource.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using UnityEngine;
5+
6+
namespace UnityVolumeRendering
7+
{
8+
/// <summary>
9+
/// Converts a directory of image slices into a VolumeDataset for volumetric rendering.
10+
/// </summary>
11+
public class ImageSequenceImporter : DatasetImporterBase
12+
{
13+
private string directoryPath;
14+
private string[] supportedImageTypes = new string[]
15+
{
16+
"*.png",
17+
"*.jpg",
18+
"*.tif",
19+
};
20+
21+
public ImageSequenceImporter(string directoryPath)
22+
{
23+
this.directoryPath = directoryPath;
24+
}
25+
26+
public override VolumeDataset Import()
27+
{
28+
if (!Directory.Exists(directoryPath))
29+
throw new NullReferenceException("No directory found: " + directoryPath);
30+
31+
List<string> imagePaths = GetSortedImagePaths();
32+
33+
if (!ImageSetHasUniformDimensions(imagePaths))
34+
throw new IndexOutOfRangeException("Image sequence has non-uniform dimensions");
35+
36+
Vector3Int dimensions = GetVolumeDimensions(imagePaths);
37+
int[] data = FillSequentialData(dimensions, imagePaths);
38+
VolumeDataset dataset = FillVolumeDataset(data, dimensions);
39+
40+
return dataset;
41+
}
42+
43+
/// <summary>
44+
/// Gets every file path in the directory with a supported suffix.
45+
/// </summary>
46+
/// /// <returns>A sorted list of image file paths.</returns>
47+
private List<string> GetSortedImagePaths()
48+
{
49+
var imagePaths = new List<string>();
50+
51+
foreach (var type in supportedImageTypes)
52+
{
53+
imagePaths.AddRange(Directory.GetFiles(directoryPath, type));
54+
}
55+
56+
imagePaths.Sort();
57+
58+
return imagePaths;
59+
}
60+
61+
/// <summary>
62+
/// Checks if every image in the set has the same XY dimensions.
63+
/// </summary>
64+
/// <param name="imagePaths">The list of image paths to check.</param>
65+
/// <returns>True if at least one image differs from another.</returns>
66+
private bool ImageSetHasUniformDimensions(List<string> imagePaths)
67+
{
68+
bool hasUniformDimension = true;
69+
70+
Vector2Int previous, current;
71+
previous = GetImageDimensions(imagePaths[0]);
72+
73+
foreach (var path in imagePaths)
74+
{
75+
current = GetImageDimensions(path);
76+
77+
if (current.x != previous.x || current.y != previous.y)
78+
{
79+
hasUniformDimension = false;
80+
break;
81+
}
82+
83+
previous = current;
84+
}
85+
86+
return hasUniformDimension;
87+
}
88+
89+
/// <summary>
90+
/// Gets the XY dimensions of an image at the path.
91+
/// </summary>
92+
/// <param name="path">The image path to check.</param>
93+
/// <returns>The XY dimensions of the image.</returns>
94+
private Vector2Int GetImageDimensions(string path)
95+
{
96+
byte[] bytes = File.ReadAllBytes(path);
97+
98+
Texture2D texture = new Texture2D(1, 1);
99+
texture.LoadImage(bytes);
100+
101+
Vector2Int dimensions = new Vector2Int()
102+
{
103+
x = texture.width,
104+
y = texture.height
105+
};
106+
107+
return dimensions;
108+
}
109+
110+
/// <summary>
111+
/// Adds a depth value Z to the XY dimensions of the first image.
112+
/// </summary>
113+
/// <param name="paths">The set of image paths comprising the volume.</param>
114+
/// <returns>The dimensions of the volume.</returns>
115+
private Vector3Int GetVolumeDimensions(List<string> paths)
116+
{
117+
Vector2Int twoDimensional = GetImageDimensions(paths[0]);
118+
Vector3Int threeDimensional = new Vector3Int()
119+
{
120+
x = twoDimensional.x,
121+
y = twoDimensional.y,
122+
z = paths.Count
123+
};
124+
return threeDimensional;
125+
}
126+
127+
/// <summary>
128+
/// Converts a volume set of images into a sequential series of values.
129+
/// </summary>
130+
/// <param name="dimensions">The XYZ dimensions of the volume.</param>
131+
/// <param name="paths">The set of image paths comprising the volume.</param>
132+
/// <returns>The set of sequential values for the volume.</returns>
133+
private int[] FillSequentialData(Vector3Int dimensions, List<string> paths)
134+
{
135+
var data = new List<int>(dimensions.x * dimensions.y * dimensions.z);
136+
var texture = new Texture2D(1, 1);
137+
138+
foreach (var path in paths)
139+
{
140+
byte[] bytes = File.ReadAllBytes(path);
141+
texture.LoadImage(bytes);
142+
143+
Color[] pixels = texture.GetPixels(); // Order priority: X -> Y -> Z
144+
int[] imageData = DensityHelper.ConvertColorsToDensities(pixels);
145+
146+
data.AddRange(imageData);
147+
}
148+
149+
return data.ToArray();
150+
}
151+
152+
/// <summary>
153+
/// Wraps volume data into a VolumeDataset.
154+
/// </summary>
155+
/// <param name="data">Sequential value data for a volume.</param>
156+
/// <param name="dimensions">The XYZ dimensions of the volume.</param>
157+
/// <returns>The wrapped volume data.</returns>
158+
private VolumeDataset FillVolumeDataset(int[] data, Vector3Int dimensions)
159+
{
160+
string name = Path.GetFileName(directoryPath);
161+
162+
VolumeDataset dataset = new VolumeDataset()
163+
{
164+
name = name,
165+
datasetName = name,
166+
data = data,
167+
dimX = dimensions.x,
168+
dimY = dimensions.y,
169+
dimZ = dimensions.z,
170+
scaleX = 1f, // Scale arbitrarily normalised around the x-axis
171+
scaleY = (float)dimensions.y / (float)dimensions.x,
172+
scaleZ = (float)dimensions.z / (float)dimensions.x
173+
};
174+
175+
return dataset;
176+
}
177+
}
178+
}

Assets/Scripts/Importing/ImageSequenceImporter.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)