Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use SimpleITK for image sequence datasets #226

Merged
merged 4 commits into from
Feb 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions Assets/Editor/VolumeRendererEditorFunctions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,58 @@ private static async void ImportNIFTIDatasetAsync(bool spawnInScene)
}
}

[MenuItem("Volume Rendering/Load dataset/Load image file")]
private static void ShowImageFileImporter()
{
ImporImageFileDatasetAsync(true);
}

[MenuItem("Assets/Volume Rendering/Import dataset/Import image file")]
private static void ImportImageFileAsset()
{
ImporImageFileDatasetAsync(false);
}

private static async void ImporImageFileDatasetAsync(bool spawnInScene)
{
string file = EditorUtility.OpenFilePanel("Select a dataset to load", "DataFiles", "");
if (File.Exists(file))
{
Debug.Log("Async dataset load. Hold on.");
using (ProgressHandler progressHandler = new ProgressHandler(new EditorProgressView(), "Image file import"))
{
progressHandler.ReportProgress(0.0f, "Importing image file dataset");

IImageFileImporter importer = ImporterFactory.CreateImageFileImporter(ImageFileFormat.Unknown);
VolumeDataset dataset = await importer.ImportAsync(file);

progressHandler.ReportProgress(0.0f, "Creating object");

if (dataset != null)
{
await OptionallyDownscale(dataset);
if (spawnInScene)
{
await VolumeObjectFactory.CreateObjectAsync(dataset);
}
else
{
ProjectWindowUtil.CreateAsset(dataset, $"{dataset.datasetName}.asset");
AssetDatabase.SaveAssets();
}
}
else
{
Debug.LogError("Failed to import datset");
}
}
}
else
{
Debug.LogError("File doesn't exist: " + file);
}
}

[MenuItem("Volume Rendering/Load dataset/Load PARCHG dataset")]
private static void ShowParDatasetImporter()
{
Expand Down Expand Up @@ -315,6 +367,11 @@ private static async void ImportSequenceAsync()

IEnumerable<IImageSequenceSeries> seriesList = await importer.LoadSeriesAsync(filePaths);

if (seriesList.Count() == 0)
{
Debug.LogWarning("Found no series to import.");
}

foreach (IImageSequenceSeries series in seriesList)
{
VolumeDataset dataset = await importer.ImportSeriesAsync(series);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ public enum ImageFileFormat
{
VASP,
NRRD,
NIFTI
NIFTI,
Unknown
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
#if UVR_USE_SIMPLEITK
using UnityEngine;
using System;
using itk.simple;
using System.Runtime.InteropServices;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;

namespace UnityVolumeRendering
{
/// <summary>
/// SimpleITK-based DICOM importer.
/// Has support for JPEG2000 and more.
/// </summary>
public class SimpleITKDICOMImporter : IImageSequenceImporter
{
public class ImageSequenceSlice : IImageSequenceFile
{
public string filePath;

public string GetFilePath()
{
return filePath;
}
}

public class ImageSequenceSeries : IImageSequenceSeries
{
public List<ImageSequenceSlice> files = new List<ImageSequenceSlice>();

public IEnumerable<IImageSequenceFile> GetFiles()
{
return files;
}
}

public IEnumerable<IImageSequenceSeries> LoadSeries(IEnumerable<string> files, ImageSequenceImportSettings settings)
{
List<ImageSequenceSeries> seriesList= LoadSeriesInternal(files);

return seriesList;
}

public async Task<IEnumerable<IImageSequenceSeries>> LoadSeriesAsync(IEnumerable<string> files, ImageSequenceImportSettings settings)
{
List<ImageSequenceSeries> seriesList = null;
await Task.Run(() => seriesList=LoadSeriesInternal(files));

return seriesList;
}

private List<ImageSequenceSeries> LoadSeriesInternal(IEnumerable<string> files)
{
HashSet<string> directories = new HashSet<string>();

foreach (string file in files)
{
string dir = Path.GetDirectoryName(file);
if (!directories.Contains(dir))
directories.Add(dir);
}

List<ImageSequenceSeries> seriesList = new List<ImageSequenceSeries>();
Dictionary<string, VectorString> directorySeries = new Dictionary<string, VectorString>();
foreach (string directory in directories)
{
VectorString seriesIDs = ImageSeriesReader.GetGDCMSeriesIDs(directory);
directorySeries.Add(directory, seriesIDs);

}

foreach (var dirSeries in directorySeries)
{
foreach (string seriesID in dirSeries.Value)
{
VectorString dicom_names = ImageSeriesReader.GetGDCMSeriesFileNames(dirSeries.Key, seriesID);
ImageSequenceSeries series = new ImageSequenceSeries();
foreach (string file in dicom_names)
{
ImageSequenceSlice sliceFile = new ImageSequenceSlice();
sliceFile.filePath = file;
series.files.Add(sliceFile);
}
seriesList.Add(series);
}
}
return seriesList;
}

public VolumeDataset ImportSeries(IImageSequenceSeries series, ImageSequenceImportSettings settings)
{
Image image = null;
float[] pixelData = null;
VectorUInt32 size = null;
VectorString dicomNames = null;

// Create dataset
VolumeDataset volumeDataset = ScriptableObject.CreateInstance<VolumeDataset>();

ImageSequenceSeries sequenceSeries = (ImageSequenceSeries)series;
if (sequenceSeries.files.Count == 0)
{
Debug.LogError("Empty series. No files to load.");
return null;
}

ImportSeriesInternal(dicomNames, sequenceSeries, image, size, pixelData, volumeDataset);

return volumeDataset;
}

public async Task<VolumeDataset> ImportSeriesAsync(IImageSequenceSeries series, ImageSequenceImportSettings settings)
{
Image image = null;
float[] pixelData = null;
VectorUInt32 size = null;
VectorString dicomNames = null;

// Create dataset
VolumeDataset volumeDataset = ScriptableObject.CreateInstance<VolumeDataset>();

ImageSequenceSeries sequenceSeries = (ImageSequenceSeries)series;
if (sequenceSeries.files.Count == 0)
{
Debug.LogError("Empty series. No files to load.");
settings.progressHandler.Fail();
return null;
}

await Task.Run(() => ImportSeriesInternal(dicomNames, sequenceSeries, image, size, pixelData, volumeDataset));

return volumeDataset;
}

private void ImportSeriesInternal(VectorString dicomNames, ImageSequenceSeries sequenceSeries, Image image, VectorUInt32 size, float[] pixelData, VolumeDataset volumeDataset)
{
ImageSeriesReader reader = new ImageSeriesReader();

dicomNames = new VectorString();

foreach (var dicomFile in sequenceSeries.files)
dicomNames.Add(dicomFile.filePath);
reader.SetFileNames(dicomNames);

image = reader.Execute();

// Cast to 32-bit float
image = SimpleITK.Cast(image, PixelIDValueEnum.sitkFloat32);

size = image.GetSize();

int numPixels = 1;
for (int dim = 0; dim < image.GetDimension(); dim++)
numPixels *= (int)size[dim];

// Read pixel data
pixelData = new float[numPixels];
IntPtr imgBuffer = image.GetBufferAsFloat();
Marshal.Copy(imgBuffer, pixelData, 0, numPixels);

for (int i = 0; i < pixelData.Length; i++)
pixelData[i] = Mathf.Clamp(pixelData[i], -1024, 3071);

VectorDouble spacing = image.GetSpacing();

volumeDataset.data = pixelData;
volumeDataset.dimX = (int)size[0];
volumeDataset.dimY = (int)size[1];
volumeDataset.dimZ = (int)size[2];
volumeDataset.datasetName = Path.GetFileName(dicomNames[0]);
volumeDataset.filePath = dicomNames[0];
volumeDataset.scale = new Vector3(
(float)(spacing[0] * size[0]) / 1000.0f, // mm to m
(float)(spacing[1] * size[1]) / 1000.0f, // mm to m
(float)(spacing[2] * size[2]) / 1000.0f // mm to m
);

// Convert from LPS to Unity's coordinate system
ImporterUtilsInternal.ConvertLPSToUnityCoordinateSpace(volumeDataset);

volumeDataset.FixDimensions();
}
}
}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
namespace UnityVolumeRendering
{
/// <summary>
/// SimpleITK-based DICOM importer.
/// Has support for JPEG2000 and more.
/// SimpleITK-based image sequence importer.
/// Has support for TIFF and more.
/// </summary>
public class SimpleITKImageSequenceImporter : IImageSequenceImporter
{
Expand Down Expand Up @@ -52,39 +52,20 @@ public async Task<IEnumerable<IImageSequenceSeries>> LoadSeriesAsync(IEnumerable

private List<ImageSequenceSeries> LoadSeriesInternal(IEnumerable<string> files)
{
HashSet<string> directories = new HashSet<string>();
ImageSequenceSeries series = new ImageSequenceSeries();

foreach (string file in files)
{
string dir = Path.GetDirectoryName(file);
if (!directories.Contains(dir))
directories.Add(dir);
}

List<ImageSequenceSeries> seriesList = new List<ImageSequenceSeries>();
Dictionary<string, VectorString> directorySeries = new Dictionary<string, VectorString>();
foreach (string directory in directories)
{
VectorString seriesIDs = ImageSeriesReader.GetGDCMSeriesIDs(directory);
directorySeries.Add(directory, seriesIDs);

}

foreach (var dirSeries in directorySeries)
{
foreach (string seriesID in dirSeries.Value)
if (File.Exists(file))
{
VectorString dicom_names = ImageSeriesReader.GetGDCMSeriesFileNames(dirSeries.Key, seriesID);
ImageSequenceSeries series = new ImageSequenceSeries();
foreach (string file in dicom_names)
{
ImageSequenceSlice sliceFile = new ImageSequenceSlice();
sliceFile.filePath = file;
series.files.Add(sliceFile);
}
seriesList.Add(series);
ImageSequenceSlice sliceFile = new ImageSequenceSlice();
sliceFile.filePath = file;
series.files.Add(sliceFile);
}
}

List<ImageSequenceSeries> seriesList = new List<ImageSequenceSeries>();
seriesList.Add(series);
return seriesList;
}

Expand All @@ -93,7 +74,6 @@ public VolumeDataset ImportSeries(IImageSequenceSeries series, ImageSequenceImpo
Image image = null;
float[] pixelData = null;
VectorUInt32 size = null;
VectorString dicomNames = null;

// Create dataset
VolumeDataset volumeDataset = ScriptableObject.CreateInstance<VolumeDataset>();
Expand All @@ -105,7 +85,7 @@ public VolumeDataset ImportSeries(IImageSequenceSeries series, ImageSequenceImpo
return null;
}

ImportSeriesInternal(dicomNames, sequenceSeries, image, size, pixelData, volumeDataset);
ImportSeriesInternal(sequenceSeries, image, size, pixelData, volumeDataset);

return volumeDataset;
}
Expand All @@ -115,7 +95,6 @@ public async Task<VolumeDataset> ImportSeriesAsync(IImageSequenceSeries series,
Image image = null;
float[] pixelData = null;
VectorUInt32 size = null;
VectorString dicomNames = null;

// Create dataset
VolumeDataset volumeDataset = ScriptableObject.CreateInstance<VolumeDataset>();
Expand All @@ -128,23 +107,28 @@ public async Task<VolumeDataset> ImportSeriesAsync(IImageSequenceSeries series,
return null;
}

await Task.Run(() => ImportSeriesInternal(dicomNames, sequenceSeries, image, size, pixelData, volumeDataset));
await Task.Run(() => ImportSeriesInternal(sequenceSeries, image, size, pixelData, volumeDataset));

return volumeDataset;
}

private void ImportSeriesInternal(VectorString dicomNames, ImageSequenceSeries sequenceSeries, Image image, VectorUInt32 size, float[] pixelData, VolumeDataset volumeDataset)
private void ImportSeriesInternal(ImageSequenceSeries sequenceSeries, Image image, VectorUInt32 size, float[] pixelData, VolumeDataset volumeDataset)
{
ImageSeriesReader reader = new ImageSeriesReader();

dicomNames = new VectorString();
VectorString fileNames = new VectorString();

foreach (var dicomFile in sequenceSeries.files)
dicomNames.Add(dicomFile.filePath);
reader.SetFileNames(dicomNames);
foreach (var file in sequenceSeries.files)
fileNames.Add(file.filePath);
reader.SetFileNames(fileNames);

image = reader.Execute();

if (image.GetDimension() > 3)
{
Debug.LogWarning("Dataset has more than 3 dimensions. Time-series are not supported. If this fails, please try import one of the files as an image file");
}

// Cast to 32-bit float
image = SimpleITK.Cast(image, PixelIDValueEnum.sitkFloat32);

Expand All @@ -168,8 +152,8 @@ private void ImportSeriesInternal(VectorString dicomNames, ImageSequenceSeries s
volumeDataset.dimX = (int)size[0];
volumeDataset.dimY = (int)size[1];
volumeDataset.dimZ = (int)size[2];
volumeDataset.datasetName = Path.GetFileName(dicomNames[0]);
volumeDataset.filePath = dicomNames[0];
volumeDataset.datasetName = Path.GetFileName(fileNames[0]);
volumeDataset.filePath = fileNames[0];
volumeDataset.scale = new Vector3(
(float)(spacing[0] * size[0]) / 1000.0f, // mm to m
(float)(spacing[1] * size[1]) / 1000.0f, // mm to m
Expand Down
Loading
Loading