Skip to content

Commit 0590306

Browse files
authored
Merge pull request mlavik1#29 from MichaelOvens/image_sequence_import
Image sequence import
2 parents 6a43f45 + d0c2cd8 commit 0590306

File tree

7 files changed

+316
-0
lines changed

7 files changed

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

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)