IMHOTEP Framework
 All Classes Namespaces Functions Variables Enumerations Enumerator Properties Pages
DicomDisplayImage.cs
1 using UnityEngine;
2 using UnityEngine.UI;
3 using UnityEngine.EventSystems;
4 using System.Collections;
5 using System.Collections.Generic;
6 using System;
7 using UI;
8 using itk.simple;
9 
10 public class DicomDisplayImage : MonoBehaviour, IScrollHandler, IPointerDownHandler, IPointerUpHandler, IPointerHoverHandler {
11 
12  private Material mMaterial;
13  //private float mMinValue;
14  //private float mMaxValue;
15 
16  // Positioning:
17  ViewSettings currentViewSettings = new ViewSettings();
18  /*private Slider mMinSlider;
19  private Slider mMaxSlider;
20  private Slider mLayerSlider;*/
21 
22  // When dragLevelWindow is true, moving the mouse will modify the windowing:
23  private bool dragLevelWindow = false;
24  // When dragPan is true, moving the mouse will modify the position:
25  private bool dragPan = false;
26  // When dragZoom is true, moving the mouse will modify the position:
27  private bool dragZoom = false;
28 
29  // Slice which is currently being loaded:
30  private bool loadingSlice = false;
31 
32  private Texture2D mDefaultMask = null;
33 
34  private DICOM2D currentDICOM;
35 
36  private bool touchpadUpPressed = false;
37  private bool touchpadDownPressed = false;
38  private float nextScrollAt = 0f;
39 
40  private struct ViewSettings
41  {
42  public float level;
43  public float window;
44  public float panX;
45  public float panY;
46  public int slice;
47  public float zoom;
48  public bool flipHorizontal;
49  public bool flipVertical;
50  }
51 
52  private Dictionary<string, ViewSettings> savedViewSettings = new Dictionary<string, ViewSettings>();
53 
54  public UI.Widget widget;
55 
56  public void OnEnable()
57  {
58  dragLevelWindow = false;
59  dragPan = false;
60  dragZoom = false;
61  LoadViewSettings ();
62 
63  //LayerChanged (currentViewSettings.layer);
64  ResetMask();
65  }
66 
67  public void OnDisable()
68  {
69  currentDICOM = null;
70  }
71 
72  public void OnScroll(PointerEventData eventData)
73  {
74  if (currentDICOM != null) {
75  //int numLayers = (int)currentDICOM.getHeader ().NumberOfImages;
76  int scrollAmount = Mathf.RoundToInt( eventData.scrollDelta.y*0.05f );
77  if( Mathf.Abs(scrollAmount) > 0 )
78  {
79  LayerChanged (currentViewSettings.slice + scrollAmount);
80  }
81  }
82  //mLayerSlider.value = mLayer;
83  }
84  public void OnPointerDown( PointerEventData eventData )
85  {
86  if( eventData.button == PointerEventData.InputButton.Left )
87  dragLevelWindow = true;
88  else if( eventData.button == PointerEventData.InputButton.Right )
89  dragPan = true;
90  else if( eventData.button == PointerEventData.InputButton.Middle )
91  dragZoom = true;
92  }
93  public void OnPointerUp( PointerEventData eventData )
94  {
95  if( eventData.button == PointerEventData.InputButton.Left )
96  dragLevelWindow = false;
97  else if( eventData.button == PointerEventData.InputButton.Right )
98  dragPan = false;
99  else if( eventData.button == PointerEventData.InputButton.Middle )
100  dragZoom = false;
101  }
102 
103  public void OnPointerHover( PointerEventData eventData )
104  {
105  if(currentDICOM != null)
106  {
107  // Cast event data to CustomEventData:
108  CustomEventData cEventData = eventData as CustomEventData;
109  if (cEventData != null) { // Just in case
110 
111  // Calculate which pixel in the dicom was hit:
112  Vector2 pixel = uvToPixel (cEventData.textureCoord);
113  // Calculate which 3D-Position (in the patient coordinate system) this pixel represents:
114  Vector3 pos3D = pixelTo3DPos (pixel);
115 
116  // Display the current position:
117  Text t = transform.Find ("PositionText").GetComponent<Text> ();
118  t.text = "(" + (int)Mathf.Round(pixel.x) + ", " + (int)Mathf.Round(pixel.y) + ", " + currentViewSettings.slice + ")";
119 
120  GameObject pointer = GameObject.Find ("3DPointer");
121  if (pointer != null)
122  pointer.transform.localPosition = pos3D;
123  }
124  }
125  }
126 
129  public Vector2 uvToPixel( Vector2 uv )
130  {
131  // Transfer the uv-coordinate in the space of the full DICOM window to
132  // uv-coordinates for the current layer:
133  Vector2 dicomUV = imageUVtoLayerUV (uv);
134  // Calculate which pixel this uv represents:
135  Vector2 pixel = new Vector3 (dicomUV.x * currentDICOM.getTexture2D ().width,
136  dicomUV.y * currentDICOM.getTexture2D ().height);
137 
138  return pixel;
139  }
140 
143  public Vector3 pixelTo3DPos( Vector2 pixel )
144  {
145  /*DICOMHeader header = currentDICOM.getHeader ();
146 
147  // Transform 2d pixel to 2d continuous pos on slice
148  Vector3 spacing = header.getSpacing ();
149  Vector2 slicePosition = Vector2.Scale (pixel, spacing);
150 
151  // Take into account the orientation of the slice:
152  Vector3 position = - header.getDirectionCosineX () * slicePosition.x
153  - header.getDirectionCosineY () * slicePosition.y;
154  // Add the image's origin (i.e. the position of the lower left pixel in 3D space):
155  Vector3 origin = header.getOrigin ();
156  position += new Vector3 (-origin.x, -origin.y, -origin.z);
157  return position;*/
158  return currentDICOM.transformPixelToPatientPos (pixel, currentViewSettings.slice);
159  }
160 
161  public Vector2 imageUVtoLayerUV( Vector2 imageUV )
162  {
163  Rect uvRect = GetComponent<RawImage> ().uvRect;
164  Vector2 uv = imageUV;
165  //uv.Scale (uvRect.size);
166  uv = uv + new Vector2( uvRect.min.x/uvRect.width, uvRect.min.y/uvRect.height );
167  uv.Scale (uvRect.size);
168  return uv;
169  }
170 
171  public void Update()
172  {
173  InputDevice inputDevice = InputDeviceManager.instance.currentInputDevice;
174  if (inputDevice.getDeviceType () == InputDeviceManager.InputDeviceType.Mouse) {
175  if (dragLevelWindow) {
176 
177  float intensityChange = -inputDevice.getTexCoordDelta ().y * 0.25f;
178  float contrastChange = inputDevice.getTexCoordDelta ().x * 0.5f;
179 
180  SetLevel (currentViewSettings.level + intensityChange);
181  SetWindow (currentViewSettings.window + contrastChange);
182  }
183  if (dragPan) {
184 
185  float dX = -inputDevice.getTexCoordDelta ().x;
186  float dY = -inputDevice.getTexCoordDelta ().y;
187  if (currentViewSettings.flipHorizontal)
188  dX = -dX;
189  if (currentViewSettings.flipVertical)
190  dY = -dY;
191 
192  currentViewSettings.panX += dX * currentViewSettings.zoom;
193  currentViewSettings.panY += dY * currentViewSettings.zoom;
194 
195  ApplyScaleAndPosition ();
196  }
197  if (dragZoom) {
198 
199  float dY = -inputDevice.getTexCoordDelta ().y * 0.5f;
200 
201  currentViewSettings.zoom = Mathf.Clamp (currentViewSettings.zoom + dY, 0.1f, 5f);
202 
203  ApplyScaleAndPosition ();
204  }
205  }
206 
207  // Let controller movement change position and zoom (if trigger is pressed):
208  if (inputDevice.getDeviceType () == InputDeviceManager.InputDeviceType.ViveController) {
209  Controller c = inputDevice as Controller;
210  if (c != null) {
211  if (c.triggerPressed ()) {
212  // Get movement delta:
213  Vector3 movement = c.positionDelta;
214 
215  // Transform the movement into the local space of the screen's Transform, to see if we're
216  // moving away from, towards, left, right, up or down relative to the screen:
217  UnityEngine.Transform tf = Platform.instance.getCenterTransformForScreen (widget.layoutPosition.screen);
218  movement = tf.InverseTransformDirection (movement);
219 
220  float dZ = -movement.z*2f*currentViewSettings.zoom;
221  currentViewSettings.zoom = Mathf.Clamp (currentViewSettings.zoom + dZ, 0.1f, 5f);
222 
223  float dX = movement.x;
224  float dY = movement.y;
225  currentViewSettings.panX += dX*currentViewSettings.zoom;
226  currentViewSettings.panY += dY*currentViewSettings.zoom;
227 
228  ApplyScaleAndPosition ();
229 
230  }
231 
232  // Clicking the touch pad scrolls single layers:
233  if( c.touchpadButtonState == UnityEngine.EventSystems.PointerEventData.FramePressState.Released ) {
234  if (c.hoverTouchpadUp()) {
235  LayerChanged (currentViewSettings.slice + 1);
236  } else if (c.hoverTouchpadDown()) {
237  LayerChanged (currentViewSettings.slice - 1);
238  }
239  touchpadDownPressed = false;
240  touchpadUpPressed = false;
241  }
242  // Pressing and holding the touchpad also scrolls:
243  if (c.touchpadButtonState == UnityEngine.EventSystems.PointerEventData.FramePressState.Pressed) {
244  if (c.hoverTouchpadUp ()) {
245  touchpadUpPressed = true;
246  } else if (c.hoverTouchpadDown ()) {
247  touchpadDownPressed = true;
248  }
249  nextScrollAt = Time.time + 0.5f; // scroll after a delay of half a second
250  }
251  if (touchpadUpPressed) {
252  if (Time.time > nextScrollAt) {
253  LayerChanged (currentViewSettings.slice + 1);
254  nextScrollAt = Time.time + 0.05f; // scroll again after a shorter delay
255  }
256  } else if (touchpadDownPressed) {
257  if (Time.time > nextScrollAt) {
258  LayerChanged (currentViewSettings.slice - 1);
259  nextScrollAt = Time.time + 0.05f; // scroll again after a shorter delay
260  }
261  }
262  }
263 
264  // Clicking the touch pad changes window/level by a fixed amount:
265  Controller lc = InputDeviceManager.instance.leftController;
266  if( lc != null )
267  {
268  if( lc.touchpadButtonState == UnityEngine.EventSystems.PointerEventData.FramePressState.Released ) {
269  if (lc.hoverTouchpadUp()) {
270  SetLevel (currentViewSettings.level - 0.05f);
271  } else if (lc.hoverTouchpadDown()) {
272  SetLevel (currentViewSettings.level + 0.05f);
273  } else if (lc.hoverTouchpadLeft()) {
274  SetWindow (currentViewSettings.window - 0.05f);
275  } else if (lc.hoverTouchpadRight()) {
276  SetWindow (currentViewSettings.window + 0.05f);
277  }
278  }
279 
280  // Scrolling on the controller also changes window/level:
281  Vector2 scrollDelta = lc.touchpadDelta * 200;
282 
283  float intensityChange = -scrollDelta.y / 2000f;
284  float contrastChange = scrollDelta.x / 2000f;
285 
286  SetLevel (currentViewSettings.level + intensityChange);
287  SetWindow (currentViewSettings.window + contrastChange);
288  }
289  }
290  }
291 
292  public void SetLevel( float newLevel )
293  {
294  currentViewSettings.level = Mathf.Clamp (newLevel, -0.5f, 1.5f);
295  UpdateLevelWindow ();
296  SaveViewSettings ();
297  }
298 
299  public void SetWindow( float newWindow )
300  {
301  currentViewSettings.window = Mathf.Clamp (newWindow, 0f, 1f);
302  UpdateLevelWindow ();
303  SaveViewSettings ();
304  }
305 
306  private void UpdateLevelWindow()
307  {
308  if (mMaterial == null)
309  return;
310 
311  mMaterial.SetFloat ("level", currentViewSettings.level);
312  mMaterial.SetFloat ("window", currentViewSettings.window);
313  }
314 
315  private void SaveViewSettings()
316  {
317  if (currentDICOM == null)
318  return;
319 
320  string seriesUID = currentDICOM.seriesInfo.seriesUID;
321  if (savedViewSettings.ContainsKey (seriesUID)) {
322  savedViewSettings [seriesUID] = currentViewSettings;
323  } else {
324  savedViewSettings.Add (seriesUID, currentViewSettings);
325  }
326  }
327 
328  private void LoadViewSettings()
329  {
330  if (currentDICOM == null)
331  return;
332 
333  string seriesUID = currentDICOM.seriesInfo.seriesUID;
334  if (savedViewSettings.ContainsKey (seriesUID)) {
335  currentViewSettings = savedViewSettings [seriesUID];
336  } else {
337  currentViewSettings = new ViewSettings {
338  level = 0.5f,
339  window = 1f,
340  panX = 0f,
341  panY = 0f,
342  slice = 0,
343  zoom = 1f,
344  flipHorizontal = false,
345  flipVertical = true
346  };
347  }
348  }
349 
351  public int savedLayerForSeriesUID( string seriesUID )
352  {
353  if (savedViewSettings.ContainsKey (seriesUID)) {
354  return savedViewSettings [seriesUID].slice;
355  } else {
356  return 0;
357  }
358  }
359 
360  /*public void MinChanged( float newVal )
361  {
362  if (mMaterial == null)
363  return;
364  mMinValue = newVal;
365  mMaterial.SetFloat ("minValue", mMinValue);
366  }
367 
368  public void MaxChanged( float newVal )
369  {
370  if (mMaterial == null)
371  return;
372  mMaxValue = newVal;
373  mMaterial.SetFloat ("maxValue", mMaxValue);
374  }*/
375 
376  public void LayerChanged( float newVal )
377  {
378  if (currentDICOM != null) {
379  // Only allow loading a new slice when the last slice-loading command has been finished:
380  if (loadingSlice == false) {
381  int numLayers = (int)currentDICOM.seriesInfo.numberOfSlices;
382  int tmpSlice = (int)Mathf.Clamp (newVal, 0, numLayers - 1);
383 
384  if (DICOMLoader.instance.startLoading (currentDICOM.seriesInfo, tmpSlice)) {
385  loadingSlice = true;
386  }
387  }
388  }
389  }
390 
391  public void SetDicom( DICOM2D dicom )
392  {
393  if (mMaterial == null) {
394  mMaterial = new Material (Shader.Find ("Unlit/DICOM2D"));
395  GetComponent<RawImage> ().material = mMaterial;
396  }
397 
398  Texture2D tex = dicom.getTexture2D ();
399  currentViewSettings.slice = dicom.slice;
400  mMaterial.SetFloat ("globalMinimum", (float)dicom.seriesInfo.minPixelValue);
401  mMaterial.SetFloat ("globalMaximum", (float)dicom.seriesInfo.maxPixelValue);
402 
403  GetComponent<RawImage> ().texture = tex;
404 
405  // If this is a new DICOM series, make sure to re-load the View Settings:
406  bool seriesChanged = false;
407  if (currentDICOM == null || currentDICOM.seriesInfo.seriesUID != dicom.seriesInfo.seriesUID)
408  seriesChanged = true;
409 
410  currentDICOM = dicom;
411  loadingSlice = false; // Allow loading a new slice
412  if( seriesChanged )
413  LoadViewSettings ();
414 
415  UpdateLevelWindow ();
416  ApplyScaleAndPosition ();
417  }
418 
419  public void ApplyScaleAndPosition()
420  {
421  if (currentDICOM == null)
422  return;
423 
424  Texture2D tex = GetComponent<RawImage> ().texture as Texture2D;
425 
426  float scaleW = 1f;
427  float scaleH = 1f;
428  // Get the pixel-spacing from the DICOM header:
429  Vector3 spacing = new Vector3 ();
430  spacing.x = (float)currentDICOM.pixelSpacing.x;
431  spacing.y = (float)currentDICOM.pixelSpacing.y;
432 
433  float imgWidth = GetComponent<RectTransform> ().rect.width;
434  float imgHeight = GetComponent<RectTransform> ().rect.height;
435  float aspectRatio = imgWidth / imgHeight;
436  //spacing.z = (float)currentDICOM.getHeader ().Spacing [2];
437  // Number of pixels multiplied with the spacing of a pixel gives the texture width/height:
438  float effectiveWidth = tex.width * spacing.x;
439  float effectiveHeight = tex.height * spacing.y;
440  // Scale to the correct aspect ratio:
441  if (effectiveWidth/imgWidth > effectiveHeight/imgHeight) {
442  scaleH = (float)effectiveWidth / (float)effectiveHeight / aspectRatio;
443  } else {
444  scaleW = (float)effectiveHeight / (float)effectiveWidth * aspectRatio;
445  }
446 
447  float oX = currentViewSettings.panX*scaleW;
448  float oY = currentViewSettings.panY*scaleH;
449 
450  if (currentViewSettings.flipHorizontal)
451  scaleW = scaleW * -1;
452  if (currentViewSettings.flipVertical)
453  scaleH = scaleH * -1;
454 
455  Rect uvRect = GetComponent<RawImage> ().uvRect;
456  uvRect.size = new Vector2 (scaleW*currentViewSettings.zoom, scaleH*currentViewSettings.zoom);
457  uvRect.center = new Vector2 (0.5f + oX, 0.5f + oY);
458  GetComponent<RawImage> ().uvRect = uvRect;
459 
460  SaveViewSettings ();
461  }
462 
463  public void FlipHorizontal()
464  {
465  Rect uvRect = GetComponent<RawImage> ().uvRect;
466  uvRect.width = -uvRect.width;
467  GetComponent<RawImage> ().uvRect = uvRect;
468  }
469  public void FlipVertical()
470  {
471  Rect uvRect = GetComponent<RawImage> ().uvRect;
472  uvRect.height = -uvRect.height;
473  GetComponent<RawImage> ().uvRect = uvRect;
474  }
475 
478  public void SetMask( Texture2D texture )
479  {
480  if (mMaterial == null) {
481  mMaterial = new Material (Shader.Find ("Unlit/DICOM2D"));
482  GetComponent<RawImage> ().material = mMaterial;
483  }
484  mMaterial.SetTexture ("_OverlayTex", texture);
485  }
486 
488  public void ResetMask()
489  {
490  if (mDefaultMask == null) {
491  // Generate a simple 1x1 texture and fill it with transparent black:
492  mDefaultMask = new Texture2D (1, 1, TextureFormat.ARGB32, false);
493  mDefaultMask.SetPixel (0, 0, new Color (0f, 0f, 0f, 0f));
494  mDefaultMask.Apply ();
495  }
496  SetMask( mDefaultMask );
497  }
498 }
static DICOMLoader instance
Definition: DICOMLoader.cs:18
Vector2 pixelSpacing
Definition: DICOM.cs:47
bool startLoading(DICOMSeries toLoad, int slice=0)
Definition: DICOMLoader.cs:127
int numberOfSlices
Definition: DICOMSeries.cs:23
int savedLayerForSeriesUID(string seriesUID)
UInt32 minPixelValue
Definition: DICOMSeries.cs:53
UInt32 maxPixelValue
Definition: DICOMSeries.cs:58
string seriesUID
Definition: DICOMSeries.cs:27
Vector3 pixelTo3DPos(Vector2 pixel)
bool triggerPressed()
Definition: Controller.cs:180
Texture2D getTexture2D()
Definition: DICOM2D.cs:217
void SetMask(Texture2D texture)
DICOMSeries seriesInfo
Definition: DICOM.cs:13
Vector2 uvToPixel(Vector2 uv)