Skip to content

Commit 3d51465

Browse files
authored
SimpleITK: DICOM JPEG, NRRD, NIFTI (Windows only) (mlavik1#95)
* SimpleITK-based DICOM importer. * Cleanup: Importer interfaces and factory. * NRRD and NIFTI import (using SimpleITK) * Added script for exporting .unitypackage * Updated documentation. * Don't use EditorDatasetImporter for RAW import. * .gitattributes
1 parent e5fc73a commit 3d51465

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+1320
-303
lines changed

Diff for: ACKNOWLEDGEMENTS.txt

+10
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,13 @@ http://opendicom.sourceforge.net/index.html
66

77
Copyright (C) 2006-2007 Albert Gnandt
88

9+
10+
SimpleITK
11+
released under Apache License 2.0
12+
https://github.com/SimpleITK/SimpleITK/blob/master/LICENSE
13+
14+
Copyright 2010-2019 Insight Software Consortium
15+
Copyright 2020 NumFOCUS
16+
17+
SimpleITK is not included in this repository, but will optionally be downloaded if the user chooser enable it.
18+
The license file will be stored together with the library in the Assets/3rdparty/SimpleITK directory.

Diff for: Assets/DONOTREMOVE-PathSearchFile.txt

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
DO NOT REMOVE. This file simply exists so we can get the path of the UnityVolumeRendering project folder (maybe be added as a subfolder to another project).
2+

Diff for: Assets/DONOTREMOVE-PathSearchFile.txt.meta

+7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: Assets/Editor/EditorDatasetImporter.cs

+28-7
Original file line numberDiff line numberDiff line change
@@ -25,22 +25,31 @@ public static void ImportDataset(string filePath)
2525
break;
2626
}
2727
case DatasetType.DICOM:
28+
case DatasetType.ImageSequence:
2829
{
30+
ImageSequenceFormat imgSeqFormat;
31+
if (datasetType == DatasetType.DICOM)
32+
imgSeqFormat = ImageSequenceFormat.DICOM;
33+
else if (datasetType == DatasetType.ImageSequence)
34+
imgSeqFormat = ImageSequenceFormat.ImageSequence;
35+
else
36+
throw new NotImplementedException();
37+
2938
string directoryPath = new FileInfo(filePath).Directory.FullName;
3039

3140
// Find all DICOM files in directory
3241
IEnumerable<string> fileCandidates = Directory.EnumerateFiles(directoryPath, "*.*", SearchOption.TopDirectoryOnly)
3342
.Where(p => p.EndsWith(".dcm", StringComparison.InvariantCultureIgnoreCase) || p.EndsWith(".dicom", StringComparison.InvariantCultureIgnoreCase) || p.EndsWith(".dicm", StringComparison.InvariantCultureIgnoreCase));
3443

35-
DICOMImporter importer = new DICOMImporter(fileCandidates, Path.GetFileName(directoryPath));
44+
IImageSequenceImporter importer = ImporterFactory.CreateImageSequenceImporter(imgSeqFormat);
3645

37-
List<DICOMImporter.DICOMSeries> seriesList = importer.LoadDICOMSeries();
38-
foreach (DICOMImporter.DICOMSeries series in seriesList)
46+
IEnumerable<IImageSequenceSeries> seriesList = importer.LoadSeries(fileCandidates);
47+
foreach (IImageSequenceSeries series in seriesList)
3948
{
4049
// Only import the series that contains the selected file
41-
if(series.dicomFiles.Any(f => Path.GetFileName(f.filePath) == Path.GetFileName(filePath)))
50+
if(series.GetFiles().Any(f => Path.GetFileName(f.GetFilePath()) == Path.GetFileName(filePath)))
4251
{
43-
VolumeDataset dataset = importer.ImportDICOMSeries(series);
52+
VolumeDataset dataset = importer.ImportSeries(series);
4453

4554
if (dataset != null)
4655
{
@@ -51,9 +60,21 @@ public static void ImportDataset(string filePath)
5160
break;
5261
}
5362
case DatasetType.PARCHG:
63+
case DatasetType.NRRD:
64+
case DatasetType.NIFTI:
5465
{
55-
ParDatasetImporter importer = new ParDatasetImporter(filePath);
56-
VolumeDataset dataset = importer.Import();
66+
ImageFileFormat imgFileFormat;
67+
if (datasetType == DatasetType.PARCHG)
68+
imgFileFormat = ImageFileFormat.VASP;
69+
else if (datasetType == DatasetType.NRRD)
70+
imgFileFormat = ImageFileFormat.NRRD;
71+
else if (datasetType == DatasetType.NIFTI)
72+
imgFileFormat = ImageFileFormat.NIFTI;
73+
else
74+
throw new NotImplementedException();
75+
76+
IImageFileImporter importer = ImporterFactory.CreateImageFileImporter(imgFileFormat);
77+
VolumeDataset dataset = importer.Import(filePath);
5778

5879
if (dataset != null)
5980
{

Diff for: Assets/Editor/ImportSettingsEditorWindow.cs

+31
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,40 @@ public static void ShowWindow()
1313

1414
private void OnGUI()
1515
{
16+
GUIStyle headerStyle = new GUIStyle(EditorStyles.label);
17+
headerStyle.fontSize = 20;
18+
19+
EditorGUILayout.LabelField("Volume rendering import settings", headerStyle);
20+
EditorGUILayout.Space();
21+
1622
EditorGUILayout.LabelField("Show promt asking if you want to downscale the dataset on import?");
1723
bool showDownscalePrompt = EditorGUILayout.Toggle("Show downscale prompt", EditorPrefs.GetBool("DownscaleDatasetPrompt"));
1824
EditorPrefs.SetBool("DownscaleDatasetPrompt", showDownscalePrompt);
25+
26+
#if UNITY_EDITOR_WIN
27+
EditorGUILayout.Space();
28+
EditorGUILayout.Space();
29+
EditorGUILayout.LabelField("SimpleITK", headerStyle);
30+
EditorGUILayout.Space();
31+
EditorGUILayout.LabelField("SimpleITK is a library that adds support for JPEG-compressed DICOM, as well as NRRD and NIFTI formats.\n" +
32+
"Enabling it will start a download of ca 100MBs of binaries. It currently only works on Windows (Linux is WIP)", EditorStyles.wordWrappedLabel);
33+
34+
if (!SimpleITKManager.IsSITKEnabled())
35+
{
36+
if (GUILayout.Button("Enable SimpleITK"))
37+
{
38+
SimpleITKManager.DownloadBinaries();
39+
SimpleITKManager.EnableSITK(true);
40+
}
41+
}
42+
else
43+
{
44+
if (GUILayout.Button("Disable SimpleITK"))
45+
{
46+
SimpleITKManager.EnableSITK(false);
47+
}
48+
}
49+
#endif
1950
}
2051
}
2152
}

Diff for: Assets/Editor/SimpleITK.meta

+8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: Assets/Editor/SimpleITK/SimpleITKManager.cs

+158
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Linq;
5+
using System.Net;
6+
using UnityEditor;
7+
using UnityEngine;
8+
using System.IO.Compression;
9+
10+
namespace UnityVolumeRendering
11+
{
12+
/// <summary>
13+
/// Manager for the SimpleITK integration.
14+
/// Since SimpleITK is a native library that requires binaries to be built for your target platform,
15+
/// SimpleITK will be disabled by default and can be enabled through this class.
16+
/// The binaries will be downloaded automatically.
17+
/// </summary>
18+
public class SimpleITKManager
19+
{
20+
private static string SimpleITKDefinition = "UVR_USE_SIMPLEITK";
21+
22+
public static bool IsSITKEnabled()
23+
{
24+
BuildTarget target = EditorUserBuildSettings.activeBuildTarget;
25+
BuildTargetGroup group = BuildPipeline.GetBuildTargetGroup(target);
26+
27+
HashSet<string> defines = new HashSet<string>(PlayerSettings.GetScriptingDefineSymbolsForGroup(group).Split(';'));
28+
return defines.Contains(SimpleITKDefinition);
29+
}
30+
31+
public static void EnableSITK(bool enable)
32+
{
33+
if (!HasDownloadedBinaries())
34+
{
35+
EditorUtility.DisplayDialog("Missing SimpleITK binaries", "You need to download the SimpleITK binaries before you can enable SimpleITK.", "Ok");
36+
return;
37+
}
38+
39+
// Enable the UVR_USE_SIMPLEITK preprocessor definition for standalone target
40+
List<BuildTargetGroup> buildTargetGroups = new List<BuildTargetGroup> (){ BuildTargetGroup.Standalone };
41+
foreach (BuildTargetGroup group in buildTargetGroups)
42+
{
43+
List<string> defines = PlayerSettings.GetScriptingDefineSymbolsForGroup(group).Split(';').ToList();
44+
defines.Remove(SimpleITKDefinition);
45+
if (enable)
46+
defines.Add(SimpleITKDefinition);
47+
PlayerSettings.SetScriptingDefineSymbolsForGroup(group, String.Join(";", defines));
48+
}
49+
50+
// Save project and recompile scripts
51+
AssetDatabase.SaveAssets();
52+
AssetDatabase.Refresh();
53+
#if UNITY_2019_3_OR_NEWER
54+
UnityEditor.Compilation.CompilationPipeline.RequestScriptCompilation();
55+
#endif
56+
}
57+
58+
public static bool HasDownloadedBinaries()
59+
{
60+
string binDir = GetBinaryDirectoryPath();
61+
return Directory.Exists(binDir) && Directory.GetFiles(binDir).Length > 0; // TODO: Check actual files?
62+
}
63+
64+
public static void DownloadBinaries()
65+
{
66+
string extractDirPath = GetBinaryDirectoryPath();
67+
string zipPath = Path.Combine(Directory.GetParent(extractDirPath).FullName, "SimpleITK.zip");
68+
if (HasDownloadedBinaries())
69+
{
70+
if (!EditorUtility.DisplayDialog("Download SimpleITK binaries", "SimpleITK has already been downloaded. Do you want to delete it and download again?", "Yes", "No"))
71+
{
72+
return;
73+
}
74+
}
75+
76+
EditorUtility.DisplayProgressBar("Downloading SimpleITK", "Downloading SimpleITK binaries.", 0);
77+
78+
// Downlaod binaries zip
79+
using (var client = new WebClient())
80+
{
81+
string downloadURL = "https://sourceforge.net/projects/simpleitk/files/SimpleITK/1.2.4/CSharp/SimpleITK-1.2.4-CSharp-win64-x64.zip/download";
82+
client.DownloadFile(downloadURL, zipPath);
83+
84+
EditorUtility.DisplayProgressBar("Downloading SimpleITK", "Downloading SimpleITK binaries.", 70);
85+
86+
if (!File.Exists(zipPath))
87+
{
88+
Debug.Log(zipPath);
89+
EditorUtility.DisplayDialog("Error downloadig SimpleITK binaries.", "Failed to download SimpleITK binaries. Please check your internet connection.", "Close");
90+
Debug.Log($"Failed to download SimpleITK binaries. You can also try to manually download from {downloadURL} and extract it to some folder inside the Assets folder.");
91+
return;
92+
}
93+
94+
try
95+
{
96+
ExtractZip(zipPath, extractDirPath);
97+
}
98+
catch (Exception ex)
99+
{
100+
string errorString = $"Extracting binaries failed with error: {ex.Message}\n"
101+
+ $"Please try downloading the zip from: {downloadURL}\nAnd extract it somewhere in the Assets folder.\n\n"
102+
+ "The download URL can be copied from the error log (console).";
103+
Debug.LogError(ex.ToString());
104+
Debug.LogError(errorString);
105+
EditorUtility.DisplayDialog("Failed to extract binaries.", errorString, "Close");
106+
}
107+
}
108+
109+
File.Delete(zipPath);
110+
111+
EditorUtility.ClearProgressBar();
112+
}
113+
114+
private static void ExtractZip(string zipPath, string extractDirPath)
115+
{
116+
// Extract zip
117+
using (FileStream zipStream = new FileStream(zipPath, FileMode.Open))
118+
{
119+
using (ZipArchive archive = new ZipArchive(zipStream, ZipArchiveMode.Update))
120+
{
121+
if (!Directory.Exists(extractDirPath))
122+
Directory.CreateDirectory(extractDirPath);
123+
124+
foreach (ZipArchiveEntry entry in archive.Entries)
125+
{
126+
if (entry.Name != "" && !entry.Name.EndsWith("/"))
127+
{
128+
string destFilePath = Path.Combine(extractDirPath, entry.Name);
129+
//TextAsset destAsset = new TextAsset("abc");
130+
//AssetDatabase.CreateAsset(destAsset, extractDirRelPath + "/" + entry.Name);
131+
Stream inStream = entry.Open();
132+
133+
using (Stream outStream = File.OpenWrite(destFilePath))
134+
{
135+
inStream.CopyTo(outStream);
136+
}
137+
}
138+
}
139+
}
140+
}
141+
}
142+
143+
private static string GetBinaryDirectoryPath()
144+
{
145+
string dataPath = Application.dataPath;
146+
foreach (string file in Directory.EnumerateFiles(Application.dataPath, "*.*", SearchOption.AllDirectories))
147+
{
148+
// Search for magic file stored in Assets directory.
149+
// This is necessary for cases where the UVR plugin is stored in a subfolder (thatæs the case for the asset store version)
150+
if (Path.GetFileName(file) == "DONOTREMOVE-PathSearchFile.txt")
151+
{
152+
dataPath = Path.GetDirectoryName(file);
153+
}
154+
}
155+
return Path.Combine(dataPath, "3rdparty", "SimpleITK"); // TODO: What is UVR is in a subfolder?
156+
}
157+
}
158+
}

Diff for: Assets/Editor/SimpleITK/SimpleITKManager.cs.meta

+11
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)