Skip to content

Commit

Permalink
SimpleITK: DICOM JPEG, NRRD, NIFTI (Windows only) (#95)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
mlavik1 committed Apr 7, 2022
1 parent e5fc73a commit 3d51465
Show file tree
Hide file tree
Showing 54 changed files with 1,320 additions and 303 deletions.
10 changes: 10 additions & 0 deletions ACKNOWLEDGEMENTS.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,13 @@ http://opendicom.sourceforge.net/index.html

Copyright (C) 2006-2007 Albert Gnandt


SimpleITK
released under Apache License 2.0
https://github.com/SimpleITK/SimpleITK/blob/master/LICENSE

Copyright 2010-2019 Insight Software Consortium
Copyright 2020 NumFOCUS

SimpleITK is not included in this repository, but will optionally be downloaded if the user chooser enable it.
The license file will be stored together with the library in the Assets/3rdparty/SimpleITK directory.
2 changes: 2 additions & 0 deletions Assets/DONOTREMOVE-PathSearchFile.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
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).

7 changes: 7 additions & 0 deletions Assets/DONOTREMOVE-PathSearchFile.txt.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

35 changes: 28 additions & 7 deletions Assets/Editor/EditorDatasetImporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,22 +25,31 @@ public static void ImportDataset(string filePath)
break;
}
case DatasetType.DICOM:
case DatasetType.ImageSequence:
{
ImageSequenceFormat imgSeqFormat;
if (datasetType == DatasetType.DICOM)
imgSeqFormat = ImageSequenceFormat.DICOM;
else if (datasetType == DatasetType.ImageSequence)
imgSeqFormat = ImageSequenceFormat.ImageSequence;
else
throw new NotImplementedException();

string directoryPath = new FileInfo(filePath).Directory.FullName;

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

DICOMImporter importer = new DICOMImporter(fileCandidates, Path.GetFileName(directoryPath));
IImageSequenceImporter importer = ImporterFactory.CreateImageSequenceImporter(imgSeqFormat);

List<DICOMImporter.DICOMSeries> seriesList = importer.LoadDICOMSeries();
foreach (DICOMImporter.DICOMSeries series in seriesList)
IEnumerable<IImageSequenceSeries> seriesList = importer.LoadSeries(fileCandidates);
foreach (IImageSequenceSeries series in seriesList)
{
// Only import the series that contains the selected file
if(series.dicomFiles.Any(f => Path.GetFileName(f.filePath) == Path.GetFileName(filePath)))
if(series.GetFiles().Any(f => Path.GetFileName(f.GetFilePath()) == Path.GetFileName(filePath)))
{
VolumeDataset dataset = importer.ImportDICOMSeries(series);
VolumeDataset dataset = importer.ImportSeries(series);

if (dataset != null)
{
Expand All @@ -51,9 +60,21 @@ public static void ImportDataset(string filePath)
break;
}
case DatasetType.PARCHG:
case DatasetType.NRRD:
case DatasetType.NIFTI:
{
ParDatasetImporter importer = new ParDatasetImporter(filePath);
VolumeDataset dataset = importer.Import();
ImageFileFormat imgFileFormat;
if (datasetType == DatasetType.PARCHG)
imgFileFormat = ImageFileFormat.VASP;
else if (datasetType == DatasetType.NRRD)
imgFileFormat = ImageFileFormat.NRRD;
else if (datasetType == DatasetType.NIFTI)
imgFileFormat = ImageFileFormat.NIFTI;
else
throw new NotImplementedException();

IImageFileImporter importer = ImporterFactory.CreateImageFileImporter(imgFileFormat);
VolumeDataset dataset = importer.Import(filePath);

if (dataset != null)
{
Expand Down
31 changes: 31 additions & 0 deletions Assets/Editor/ImportSettingsEditorWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,40 @@ public static void ShowWindow()

private void OnGUI()
{
GUIStyle headerStyle = new GUIStyle(EditorStyles.label);
headerStyle.fontSize = 20;

EditorGUILayout.LabelField("Volume rendering import settings", headerStyle);
EditorGUILayout.Space();

EditorGUILayout.LabelField("Show promt asking if you want to downscale the dataset on import?");
bool showDownscalePrompt = EditorGUILayout.Toggle("Show downscale prompt", EditorPrefs.GetBool("DownscaleDatasetPrompt"));
EditorPrefs.SetBool("DownscaleDatasetPrompt", showDownscalePrompt);

#if UNITY_EDITOR_WIN
EditorGUILayout.Space();
EditorGUILayout.Space();
EditorGUILayout.LabelField("SimpleITK", headerStyle);
EditorGUILayout.Space();
EditorGUILayout.LabelField("SimpleITK is a library that adds support for JPEG-compressed DICOM, as well as NRRD and NIFTI formats.\n" +
"Enabling it will start a download of ca 100MBs of binaries. It currently only works on Windows (Linux is WIP)", EditorStyles.wordWrappedLabel);

if (!SimpleITKManager.IsSITKEnabled())
{
if (GUILayout.Button("Enable SimpleITK"))
{
SimpleITKManager.DownloadBinaries();
SimpleITKManager.EnableSITK(true);
}
}
else
{
if (GUILayout.Button("Disable SimpleITK"))
{
SimpleITKManager.EnableSITK(false);
}
}
#endif
}
}
}
8 changes: 8 additions & 0 deletions Assets/Editor/SimpleITK.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

158 changes: 158 additions & 0 deletions Assets/Editor/SimpleITK/SimpleITKManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using UnityEditor;
using UnityEngine;
using System.IO.Compression;

namespace UnityVolumeRendering
{
/// <summary>
/// Manager for the SimpleITK integration.
/// Since SimpleITK is a native library that requires binaries to be built for your target platform,
/// SimpleITK will be disabled by default and can be enabled through this class.
/// The binaries will be downloaded automatically.
/// </summary>
public class SimpleITKManager
{
private static string SimpleITKDefinition = "UVR_USE_SIMPLEITK";

public static bool IsSITKEnabled()
{
BuildTarget target = EditorUserBuildSettings.activeBuildTarget;
BuildTargetGroup group = BuildPipeline.GetBuildTargetGroup(target);

HashSet<string> defines = new HashSet<string>(PlayerSettings.GetScriptingDefineSymbolsForGroup(group).Split(';'));
return defines.Contains(SimpleITKDefinition);
}

public static void EnableSITK(bool enable)
{
if (!HasDownloadedBinaries())
{
EditorUtility.DisplayDialog("Missing SimpleITK binaries", "You need to download the SimpleITK binaries before you can enable SimpleITK.", "Ok");
return;
}

// Enable the UVR_USE_SIMPLEITK preprocessor definition for standalone target
List<BuildTargetGroup> buildTargetGroups = new List<BuildTargetGroup> (){ BuildTargetGroup.Standalone };
foreach (BuildTargetGroup group in buildTargetGroups)
{
List<string> defines = PlayerSettings.GetScriptingDefineSymbolsForGroup(group).Split(';').ToList();
defines.Remove(SimpleITKDefinition);
if (enable)
defines.Add(SimpleITKDefinition);
PlayerSettings.SetScriptingDefineSymbolsForGroup(group, String.Join(";", defines));
}

// Save project and recompile scripts
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
#if UNITY_2019_3_OR_NEWER
UnityEditor.Compilation.CompilationPipeline.RequestScriptCompilation();
#endif
}

public static bool HasDownloadedBinaries()
{
string binDir = GetBinaryDirectoryPath();
return Directory.Exists(binDir) && Directory.GetFiles(binDir).Length > 0; // TODO: Check actual files?
}

public static void DownloadBinaries()
{
string extractDirPath = GetBinaryDirectoryPath();
string zipPath = Path.Combine(Directory.GetParent(extractDirPath).FullName, "SimpleITK.zip");
if (HasDownloadedBinaries())
{
if (!EditorUtility.DisplayDialog("Download SimpleITK binaries", "SimpleITK has already been downloaded. Do you want to delete it and download again?", "Yes", "No"))
{
return;
}
}

EditorUtility.DisplayProgressBar("Downloading SimpleITK", "Downloading SimpleITK binaries.", 0);

// Downlaod binaries zip
using (var client = new WebClient())
{
string downloadURL = "https://sourceforge.net/projects/simpleitk/files/SimpleITK/1.2.4/CSharp/SimpleITK-1.2.4-CSharp-win64-x64.zip/download";
client.DownloadFile(downloadURL, zipPath);

EditorUtility.DisplayProgressBar("Downloading SimpleITK", "Downloading SimpleITK binaries.", 70);

if (!File.Exists(zipPath))
{
Debug.Log(zipPath);
EditorUtility.DisplayDialog("Error downloadig SimpleITK binaries.", "Failed to download SimpleITK binaries. Please check your internet connection.", "Close");
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.");
return;
}

try
{
ExtractZip(zipPath, extractDirPath);
}
catch (Exception ex)
{
string errorString = $"Extracting binaries failed with error: {ex.Message}\n"
+ $"Please try downloading the zip from: {downloadURL}\nAnd extract it somewhere in the Assets folder.\n\n"
+ "The download URL can be copied from the error log (console).";
Debug.LogError(ex.ToString());
Debug.LogError(errorString);
EditorUtility.DisplayDialog("Failed to extract binaries.", errorString, "Close");
}
}

File.Delete(zipPath);

EditorUtility.ClearProgressBar();
}

private static void ExtractZip(string zipPath, string extractDirPath)
{
// Extract zip
using (FileStream zipStream = new FileStream(zipPath, FileMode.Open))
{
using (ZipArchive archive = new ZipArchive(zipStream, ZipArchiveMode.Update))
{
if (!Directory.Exists(extractDirPath))
Directory.CreateDirectory(extractDirPath);

foreach (ZipArchiveEntry entry in archive.Entries)
{
if (entry.Name != "" && !entry.Name.EndsWith("/"))
{
string destFilePath = Path.Combine(extractDirPath, entry.Name);
//TextAsset destAsset = new TextAsset("abc");
//AssetDatabase.CreateAsset(destAsset, extractDirRelPath + "/" + entry.Name);
Stream inStream = entry.Open();

using (Stream outStream = File.OpenWrite(destFilePath))
{
inStream.CopyTo(outStream);
}
}
}
}
}
}

private static string GetBinaryDirectoryPath()
{
string dataPath = Application.dataPath;
foreach (string file in Directory.EnumerateFiles(Application.dataPath, "*.*", SearchOption.AllDirectories))
{
// Search for magic file stored in Assets directory.
// This is necessary for cases where the UVR plugin is stored in a subfolder (thatæs the case for the asset store version)
if (Path.GetFileName(file) == "DONOTREMOVE-PathSearchFile.txt")
{
dataPath = Path.GetDirectoryName(file);
}
}
return Path.Combine(dataPath, "3rdparty", "SimpleITK"); // TODO: What is UVR is in a subfolder?
}
}
}
11 changes: 11 additions & 0 deletions Assets/Editor/SimpleITK/SimpleITKManager.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 3d51465

Please sign in to comment.