Skip to content

Commit

Permalink
Properly handle coordinate systems on import (#170)
Browse files Browse the repository at this point in the history
* Reverse pixel array to correct coordinate space. NRRD: Convert to LPS space first.

* Convert from LPS to Unity coordinates

* Make sure we don't break old serialised objects

* Select slicing plane on add

* Make sure imported datasets will have the same rotation as before. Removed 90 degree rotation from outer object - do this in each importer instead.
  • Loading branch information
mlavik1 committed Apr 25, 2023
1 parent d72e2ae commit 3bb6a50
Show file tree
Hide file tree
Showing 17 changed files with 153 additions and 49 deletions.
25 changes: 18 additions & 7 deletions Assets/Editor/SliceRenderingEditorWindow.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using UnityEngine;
using UnityEditor;
using System.Linq;

namespace UnityVolumeRendering
{
Expand Down Expand Up @@ -62,6 +63,13 @@ private void OnGUI()

if (spawnedPlanes.Length > 0)
selectedPlaneIndex = selectedPlaneIndex % spawnedPlanes.Length;

if (Selection.activeGameObject != null)
{
int index = System.Array.FindIndex(spawnedPlanes, plane => plane.gameObject == Selection.activeGameObject);
if (index != -1)
selectedPlaneIndex = index;
}

if (GUI.Toggle(new Rect(0.0f, 0.0f, 40.0f, 40.0f), inputMode == InputMode.Move, new GUIContent(moveIconTexture, "Move slice"), GUI.skin.button))
inputMode = InputMode.Move;
Expand All @@ -77,7 +85,7 @@ private void OnGUI()
SlicingPlane planeObj = spawnedPlanes[System.Math.Min(selectedPlaneIndex, spawnedPlanes.Length - 1)];
Vector3 planeScale = planeObj.transform.lossyScale;

float heightWidthRatio = planeScale.z / planeScale.x;
float heightWidthRatio = Mathf.Abs(planeScale.z / planeScale.x);
float bgWidth = Mathf.Min(this.position.width - 20.0f, (this.position.height - 50.0f) * 2.0f);
float bgHeight = Mathf.Min(bgWidth, this.position.height - 150.0f);
bgWidth = bgHeight / heightWidthRatio;
Expand All @@ -102,7 +110,7 @@ private void OnGUI()
{
Vector2 mouseOffset = relMousePosNormalised - prevMousePos;
if (Mathf.Abs(mouseOffset.y) > 0.00001f)
planeObj.transform.Translate(Vector3.up * mouseOffset.y);
planeObj.transform.Translate(planeObj.transform.up * mouseOffset.y, Space.World);
}
// Show value at mouse position.
else if (inputMode == InputMode.Inspect)
Expand Down Expand Up @@ -152,7 +160,7 @@ private void OnGUI()

if (GUI.Button(new Rect(0.0f, bgRect.y + bgRect.height + 40.0f, 70.0f, 20.0f), "<"))
{
selectedPlaneIndex = (selectedPlaneIndex - 1) % spawnedPlanes.Length;
selectedPlaneIndex = selectedPlaneIndex == 0 ? spawnedPlanes.Length - 1 : selectedPlaneIndex - 1;
Selection.activeGameObject = spawnedPlanes[selectedPlaneIndex].gameObject;
}
if (GUI.Button(new Rect(90.0f, bgRect.y + bgRect.height + 40.0f, 70.0f, 20.0f), ">"))
Expand All @@ -169,19 +177,22 @@ private void OnGUI()
if (GUI.Button(new Rect(200.0f, bgRect.y + bgRect.height + 0.0f, 120.0f, 20.0f), "Create XY plane"))
{
selectedPlaneIndex = spawnedPlanes.Length;
volRend.CreateSlicingPlane();
SlicingPlane plane = volRend.CreateSlicingPlane();
UnityEditor.Selection.objects = new UnityEngine.Object[] { plane.gameObject };
}
else if (GUI.Button(new Rect(200.0f, bgRect.y + bgRect.height + 20.0f, 120.0f, 20.0f), "Create XZ plane"))
{
selectedPlaneIndex = spawnedPlanes.Length;
SlicingPlane plane = volRend.CreateSlicingPlane();
plane.transform.localRotation = Quaternion.Euler(90.0f, 0.0f, 0.0f);
UnityEditor.Selection.objects = new UnityEngine.Object[] { plane.gameObject };
}
else if (GUI.Button(new Rect(200.0f, bgRect.y + bgRect.height + 40.0f, 120.0f, 20.0f), "Create ZY plane"))
{
selectedPlaneIndex = spawnedPlanes.Length;
SlicingPlane plane = volRend.CreateSlicingPlane();
plane.transform.localRotation = Quaternion.Euler(0.0f, 0.0f, 90.0f);
UnityEditor.Selection.objects = new UnityEngine.Object[] { plane.gameObject };
}
}

Expand Down Expand Up @@ -215,16 +226,16 @@ private Vector3 GetWorldPosition(Vector2 relativeMousePosition, SlicingPlane sli
private Vector3 GetDataPosition(Vector2 relativeMousePosition, SlicingPlane slicingPlane)
{
Vector3 worldSpacePosition = GetWorldPosition(relativeMousePosition, slicingPlane);
Vector3 objSpacePoint = slicingPlane.targetObject.transform.InverseTransformPoint(worldSpacePosition);
Vector3 objSpacePoint = slicingPlane.targetObject.volumeContainerObject.transform.InverseTransformPoint(worldSpacePosition);
Vector3 uvw = objSpacePoint + Vector3.one * 0.5f;
VolumeDataset dataset = slicingPlane.targetObject.dataset;
return new Vector3(uvw.x * dataset.scaleX, uvw.y * dataset.scaleY,uvw.z * dataset.scaleZ);
return new Vector3(uvw.x * dataset.scale.x, uvw.y * dataset.scale.y, uvw.z * dataset.scale.z);
}

private float GetValueAtPosition(Vector2 relativeMousePosition, SlicingPlane slicingPlane)
{
Vector3 worldSpacePosition = GetWorldPosition(relativeMousePosition, slicingPlane);
Vector3 objSpacePoint = slicingPlane.targetObject.transform.InverseTransformPoint(worldSpacePosition);
Vector3 objSpacePoint = slicingPlane.targetObject.volumeContainerObject.transform.InverseTransformPoint(worldSpacePosition);
VolumeDataset dataset = slicingPlane.targetObject.dataset;
// Convert to texture coordinates.
Vector3 uvw = objSpacePoint + Vector3.one * 0.5f;
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,10 @@ private void ImportInternal(VolumeDataset volumeDataset,Nifti.NET.Nifti niftiFil
volumeDataset.dimZ = dimZ;
volumeDataset.datasetName = "test";
volumeDataset.filePath = filePath;
volumeDataset.scaleX = size.x;
volumeDataset.scaleY = size.y;
volumeDataset.scaleZ = size.z;
volumeDataset.scale = size;

volumeDataset.FixDimensions();
volumeDataset.rotation = Quaternion.Euler(90.0f, 0.0f, 0.0f);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ private void ImportInternal(VolumeDataset volumeDataset, float[] pixelData,Vecto

Image image = reader.Execute();

// Convert to LPS coordinate system (may be needed for NRRD and other datasets)
SimpleITK.DICOMOrient(image, "LPS");

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

Expand All @@ -62,18 +65,23 @@ private void ImportInternal(VolumeDataset volumeDataset, float[] pixelData,Vecto
pixelData = new float[numPixels];
IntPtr imgBuffer = image.GetBufferAsFloat();
Marshal.Copy(imgBuffer, pixelData, 0, numPixels);
spacing = image.GetSpacing();

spacing = image.GetSpacing();

volumeDataset.data = pixelData;
volumeDataset.dimX = (int)size[0];
volumeDataset.dimY = (int)size[1];
volumeDataset.dimZ = (int)size[2];
volumeDataset.datasetName = "test";
volumeDataset.filePath = filePath;
volumeDataset.scaleX = (float)(spacing[0] * size[0]);
volumeDataset.scaleY = (float)(spacing[1] * size[1]);
volumeDataset.scaleZ = (float)(spacing[2] * size[2]);
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();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ private void ImportInternal(VolumeDataset dataFiller)
{
dataFiller.data[i] = dataGrid[i];
}
dataFiller.rotation = Quaternion.Euler(90.0f, 0.0f, 0.0f);
}

private string ParseLine()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ public VolumeDataset ImportSeries(IImageSequenceSeries series, ImageSequenceImpo
VolumeDataset dataset = FillVolumeDataset(data, dimensions);

dataset.FixDimensions();
dataset.rotation = Quaternion.Euler(90.0f, 0.0f, 0.0f);

return dataset;
}
Expand Down Expand Up @@ -211,9 +212,11 @@ private void FillVolumeInternal(VolumeDataset dataset,string name,int[] data, Ve
dataset.dimX = dimensions.x;
dataset.dimY = dimensions.y;
dataset.dimZ = dimensions.z;
dataset.scaleX = 1f; // Scale arbitrarily normalised around the x-axis
dataset.scaleY = (float)dimensions.y / (float)dimensions.x;
dataset.scaleZ = (float)dimensions.z / (float)dimensions.x;
dataset.scale = new Vector3(
1f, // Scale arbitrarily normalised around the x-axis
(float)dimensions.y / (float)dimensions.x,
(float)dimensions.z / (float)dimensions.x
);
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,12 +222,15 @@ private void ImportSeriesInternal(List<DICOMSliceFile> files,VolumeDataset datas

if (files[0].pixelSpacing > 0.0f)
{
dataset.scaleX = files[0].pixelSpacing * dataset.dimX;
dataset.scaleY = files[0].pixelSpacing * dataset.dimY;
dataset.scaleZ = Mathf.Abs(files[files.Count - 1].location - files[0].location);
dataset.scale = new Vector3(
files[0].pixelSpacing * dataset.dimX,
files[0].pixelSpacing * dataset.dimY,
Mathf.Abs(files[files.Count - 1].location - files[0].location)
);
}

dataset.FixDimensions();
dataset.rotation = Quaternion.Euler(90.0f, 0.0f, 0.0f);
}

private DICOMSliceFile ReadDICOMFile(string filePath)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ public async Task<VolumeDataset> ImportSeriesAsync(IImageSequenceSeries series,
return volumeDataset;
}

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

Expand Down Expand Up @@ -162,6 +162,7 @@ private void ImportSeriesInternal(VectorString dicomNames, ImageSequenceSeries s

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

VectorDouble spacing = image.GetSpacing();

volumeDataset.data = pixelData;
Expand All @@ -170,9 +171,14 @@ private void ImportSeriesInternal(VectorString dicomNames, ImageSequenceSeries s
volumeDataset.dimZ = (int)size[2];
volumeDataset.datasetName = "test";
volumeDataset.filePath = dicomNames[0];
volumeDataset.scaleX = (float)(spacing[0] * size[0]);
volumeDataset.scaleY = (float)(spacing[1] * size[1]);
volumeDataset.scaleZ = (float)(spacing[2] * size[2]);
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();
}
Expand Down
20 changes: 20 additions & 0 deletions Assets/Scripts/Importing/ImporterUtilsInternal.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System.IO;
using UnityEngine;
using System;
using UnityEditor;

namespace UnityVolumeRendering
{
public class ImporterUtilsInternal
{
public static void ConvertLPSToUnityCoordinateSpace(VolumeDataset volumeDataset)
{
volumeDataset.scale = new Vector3(
-volumeDataset.scale.x,
volumeDataset.scale.y,
volumeDataset.scale.z
);
volumeDataset.rotation = Quaternion.Euler(90.0f, 0.0f, 0.0f);
}
}
}
11 changes: 11 additions & 0 deletions Assets/Scripts/Importing/ImporterUtilsInternal.cs.meta

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

4 changes: 2 additions & 2 deletions Assets/Scripts/Importing/RawImporter/RawDatasetImporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,7 @@ await Task.Run(() => {
}
VolumeDataset dataset = new VolumeDataset();


await Task.Run(() => ImportInternal(dataset,reader,fs));
await Task.Run(() => ImportInternal(dataset, reader, fs));

return dataset;
}
Expand Down Expand Up @@ -126,6 +125,7 @@ private void ImportInternal(VolumeDataset dataset, BinaryReader reader, FileStre
fs.Close();

dataset.FixDimensions();
dataset.rotation = Quaternion.Euler(90.0f, 0.0f, 0.0f);
}
private int ReadDataValue(BinaryReader reader)
{
Expand Down
30 changes: 25 additions & 5 deletions Assets/Scripts/VolumeData/VolumeDataset.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@
using Unity.Collections;
using UnityEditor;
using UnityEngine;
using UnityEngine.Serialization;

namespace UnityVolumeRendering
{
/// <summary>
/// An imported dataset. Has a dimension and a 3D pixel array.
/// </summary>
[Serializable]
public class VolumeDataset : ScriptableObject
public class VolumeDataset : ScriptableObject, ISerializationCallbackReceiver
{
public string filePath;

Expand All @@ -24,11 +25,10 @@ public class VolumeDataset : ScriptableObject
public int dimX, dimY, dimZ;

[SerializeField]
public float scaleX = 1.0f;
[SerializeField]
public float scaleY = 1.0f;
public Vector3 scale = Vector3.one;

[SerializeField]
public float scaleZ = 1.0f;
public Quaternion rotation;

public float volumeScale;

Expand All @@ -44,6 +44,13 @@ public class VolumeDataset : ScriptableObject
private SemaphoreSlim createDataTextureLock = new SemaphoreSlim(1, 1);
private SemaphoreSlim createGradientTextureLock = new SemaphoreSlim(1, 1);

[SerializeField, System.Obsolete("Use scale instead")]
private float scaleX = 1.0f;
[SerializeField, System.Obsolete("Use scale instead")]
private float scaleY = 1.0f;
[SerializeField, System.Obsolete("Use scale instead")]
private float scaleZ = 1.0f;

public Texture3D GetDataTexture()
{
if (dataTexture == null)
Expand Down Expand Up @@ -360,5 +367,18 @@ public float GetData(int x, int y, int z)
{
return data[x + y * dimX + z * (dimX * dimY)];
}

public void OnBeforeSerialize()
{
scaleX = scale.x;
scaleY = scale.y;
scaleZ = scale.z;
}

public void OnAfterDeserialize()
{
scale = new Vector3(scaleX, scaleY, scaleZ);
Debug.Log(scale);
}
}
}
2 changes: 1 addition & 1 deletion Assets/Scripts/VolumeObject/CrossSectionPlane.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public CrossSectionType GetCrossSectionType()

public Matrix4x4 GetMatrix()
{
return transform.worldToLocalMatrix * targetObject.transform.localToWorldMatrix;
return transform.worldToLocalMatrix * targetObject.volumeContainerObject.transform.localToWorldMatrix;
}
}
}
2 changes: 1 addition & 1 deletion Assets/Scripts/VolumeObject/CutoutBox.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public CrossSectionType GetCrossSectionType()

public Matrix4x4 GetMatrix()
{
return transform.worldToLocalMatrix * targetObject.transform.localToWorldMatrix;
return transform.worldToLocalMatrix * targetObject.volumeContainerObject.transform.localToWorldMatrix;
}
}
}
2 changes: 1 addition & 1 deletion Assets/Scripts/VolumeObject/CutoutSphere.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public CrossSectionType GetCrossSectionType()

public Matrix4x4 GetMatrix()
{
return transform.worldToLocalMatrix * targetObject.transform.localToWorldMatrix;
return transform.worldToLocalMatrix * targetObject.volumeContainerObject.transform.localToWorldMatrix;
}

private void OnEnable()
Expand Down
Loading

0 comments on commit 3bb6a50

Please sign in to comment.