IMHOTEP Framework
 All Classes Namespaces Functions Variables Enumerations Enumerator Properties Pages
DICOMSeries.cs
1 
2 using itk.simple;
3 using UnityEngine;
4 using System;
5 
6 
7 public enum SliceOrientation
8 {
9  Transverse,
10  Saggital,
11  Coronal,
12  Unknown
13 };
14 
15 
20 public class DICOMSeries {
21 
23  public int numberOfSlices { private set; get; }
25  public VectorString filenames { private set; get; }
27  public string seriesUID { private set; get; }
28 
30  public Image firstSlice { private set; get; }
32  public Image lastSlice { private set; get; }
33 
42  public SliceOrientation sliceOrientation { private set; get; }
43 
47  public Vector3 sliceOffset { private set; get; }
48 
53  public UInt32 minPixelValue { private set; get; }
58  public UInt32 maxPixelValue { private set; get; }
59 
61  public bool foundMinMaxPixelValues { private set; get; }
62 
64  private string description = null;
65 
71  public Vector3 directionCosineX { private set; get; }
77  public Vector3 directionCosineY { private set; get; }
78 
80  public bool isConsecutiveVolume { private set; get; }
81 
82 
84  public Vector3 sliceNormal { private set; get; }
85 
90  public DICOMSeries( string directory, string seriesUID )
91  {
92  Debug.Log ("Loading Meta Data for Series: " + seriesUID);
93  // Get the file names for the series:
94  filenames = ImageSeriesReader.GetGDCMSeriesFileNames( directory, seriesUID );
95  if (filenames.Count <= 0) {
96  throw( new System.Exception ("No files found for series " + seriesUID + "."));
97  }
98  this.seriesUID = seriesUID;
99 
100  // Load the first slice in volume to get meta information:
101  firstSlice = SimpleITK.ReadImage( filenames[0] );
102  lastSlice = SimpleITK.ReadImage( filenames[Math.Max(filenames.Count-1,0)] );
103 
104  numberOfSlices = filenames.Count;
105 
106  // Load the direction cosines:
107  // ITK stores the direction cosines in a matrix with row-major-ordering. The weird indexing is because
108  // we need the first and second column (0,3,6 for X and 1,4,7 for Y)
109  VectorDouble direction = firstSlice.GetDirection();
110  if( direction.Count < 6 )
111  throw( new System.Exception ("Invalid direction cosines found in images."));
112  directionCosineX = new Vector3 ((float)direction [0], (float)direction [3], (float)direction [6]);
113  directionCosineY = new Vector3 ((float)direction [1], (float)direction [4], (float)direction [7]);
114 
115  sliceNormal = Vector3.Cross (directionCosineX, directionCosineY);
116 
117  if (lastSlice != null) {
118 
119  // Get the origins of the two images:
120  VectorDouble o1 = firstSlice.GetOrigin();
121  if ( o1.Count < 3) {
122  throw( new System.Exception ("Invalid origins found in first image."));
123  }
124  Vector3 origin = new Vector3 ((float)o1 [0], (float)o1 [1], (float)o1 [2]);
125  VectorDouble o2 = lastSlice.GetOrigin ();
126  if ( o2.Count < 3) {
127  throw( new System.Exception ("Invalid origins found in last image."));
128  }
129  Vector3 lastOrigin = new Vector3 ((float)o2 [0], (float)o2 [1], (float)o2 [2]);
130 
131  // Calculate offset between two adjacent slices (assuming all neighbours are the same distance apart):
132  // Note: I expect sliceOffset.x and sliceOffset.y to be zero most of the time.
133  // Using a Vector just for completeness.
134  sliceOffset = (lastOrigin - origin) / (numberOfSlices - 1);
135  }
136 
137  if (lastSlice != null && numberOfSlices > 1) {
138  // Load the direction cosines:
139  // ITK stores the direction cosines in a matrix with row-major-ordering. The weird indexing is because
140  // we need the first and second column (0,3,6 for X and 1,4,7 for Y)
141  VectorDouble directionLast = lastSlice.GetDirection ();
142  if (directionLast.Count < 6)
143  throw(new System.Exception ("Invalid direction cosines found in images."));
144  Vector3 directionCosineXLast = new Vector3 ((float)directionLast [0], (float)directionLast [3], (float)directionLast [6]);
145  Vector3 directionCosineYLast = new Vector3 ((float)directionLast [1], (float)directionLast [4], (float)directionLast [7]);
146 
147  Vector3 sliceNormalLast = Vector3.Cross (directionCosineXLast, directionCosineYLast);
148 
149  // If the first and last slice have the same orientation, then consider this series to be a volume.
150  // TODO: Better check?
151  if ((sliceNormal == sliceNormalLast)) {
152  isConsecutiveVolume = true;
153  } else {
154  Debug.LogWarning ("First and last slice of the series do not have the same orientation. This will not be considered a volume.\n" +
155  "\tNormal first slice, Normal last slice: " + sliceNormal + " " + sliceNormalLast);
156  }
157 
158  } else {
159  isConsecutiveVolume = false;
160  }
161 
162  if( isConsecutiveVolume ) {
163  // Calculate which direction the normal is facing to determine the orienation (Transverse,
164  // Coronal or Saggital).
165  float absX = Mathf.Abs (sliceNormal.x);
166  float absY = Mathf.Abs (sliceNormal.y);
167  float absZ = Mathf.Abs (sliceNormal.z);
168  if (absX > absY && absX > absZ) {
169  sliceOrientation = SliceOrientation.Saggital;
170  } else if (absY > absX && absY > absZ) {
171  sliceOrientation = SliceOrientation.Coronal;
172  } else if (absZ > absX && absZ > absY) {
173  sliceOrientation = SliceOrientation.Transverse;
174  } else {
175  sliceOrientation = SliceOrientation.Unknown;
176  }
177  } else {
178  sliceOrientation = SliceOrientation.Unknown; // Can't know what the orientation is if the first and last slice have different normals
179  }
180 
181  // Read the minimum and maximum values which are stored in this image:
182  minPixelValue = UInt16.MinValue;
183  maxPixelValue = UInt16.MaxValue;
184  foundMinMaxPixelValues = false;
185  try {
186  minPixelValue = UInt32.Parse( firstSlice.GetMetaData("0028|0106") );
187  maxPixelValue = UInt32.Parse( firstSlice.GetMetaData("0028|0107") );
188  foundMinMaxPixelValues = true;
189  } catch {
190  }
191  }
192 
194  public string getDescription()
195  {
196  try{
197  // If the description was already generated earlier, re-use it:
198  if (description != null && description.Length > 0)
199  return description;
200  description = "";
201 
202  string modality = "";
203  string acquisitionContextDescription = "";
204  string seriesDescription = "";
205  string imageComment = "";
206  string bodyPartExamined = "";
207  string acquisitionDate = "";
208 
209  try {
210  modality = firstSlice.GetMetaData ("0008|0060");
211  } catch {
212  }
213  try {
214  acquisitionContextDescription = firstSlice.GetMetaData ("0040|0556");
215  } catch {
216  }
217  try {
218  seriesDescription = firstSlice.GetMetaData ("0008|103E");
219  } catch {
220  }
221  try {
222  imageComment = firstSlice.GetMetaData ("0020|4000");
223  } catch {
224  }
225  try {
226  bodyPartExamined = firstSlice.GetMetaData ("0018|0015");
227  } catch {
228  }
229  try {
230  acquisitionDate = firstSlice.GetMetaData ("0008|0022");
231  if (acquisitionDate != null) {
232  DateTime time = DateTime.ParseExact (acquisitionDate, "yyyyMMdd",
233  System.Globalization.CultureInfo.InvariantCulture);
234  acquisitionDate = time.ToString ("dd MMMM yyyy");
235  }
236  } catch {
237  }
238 
239 
240  if (modality.Length > 0)
241  description += "[" + modality + "]";
242 
243  if (bodyPartExamined.Length > 0)
244  description += " " + bodyPartExamined;
245  description += " " + sliceOrientation;
246  if (acquisitionDate != null && acquisitionDate.Length > 0)
247  description += ", " + acquisitionDate;
248  description += " (" + numberOfSlices + " images)";
249 
250  if (imageComment != null && imageComment.Length > 0)
251  description += "\n<color=#dddddd>\t" + imageComment + "</color>";
252  else if (seriesDescription != null && seriesDescription.Length > 0)
253  description += "\n<color=#dddddd>\t" + seriesDescription + "</color>";
254  else if (acquisitionContextDescription != null && acquisitionContextDescription.Length > 0)
255  description += "\n<color=#dddddd>\t" + acquisitionContextDescription + "</color>";
256 
257  } catch {
258  description = "Failed to generate DICOM description (valid DICOM?).";
259  }
260  return description;
261  }
262 
265  public void setMinMaxPixelValues( UInt32 min, UInt32 max )
266  {
267  minPixelValue = min;
268  maxPixelValue = max;
269  foundMinMaxPixelValues = true;
270  }
271 }
Image lastSlice
Definition: DICOMSeries.cs:32
bool isConsecutiveVolume
Definition: DICOMSeries.cs:80
VectorString filenames
Definition: DICOMSeries.cs:25
SliceOrientation sliceOrientation
Definition: DICOMSeries.cs:42
DICOMSeries(string directory, string seriesUID)
Definition: DICOMSeries.cs:90
bool foundMinMaxPixelValues
Definition: DICOMSeries.cs:61
Vector3 sliceNormal
Definition: DICOMSeries.cs:84
int numberOfSlices
Definition: DICOMSeries.cs:23
UInt32 minPixelValue
Definition: DICOMSeries.cs:53
Vector3 directionCosineX
Definition: DICOMSeries.cs:71
UInt32 maxPixelValue
Definition: DICOMSeries.cs:58
string seriesUID
Definition: DICOMSeries.cs:27
string getDescription()
Definition: DICOMSeries.cs:194
Vector3 sliceOffset
Definition: DICOMSeries.cs:47
Vector3 directionCosineY
Definition: DICOMSeries.cs:77
Image firstSlice
Definition: DICOMSeries.cs:30
void setMinMaxPixelValues(UInt32 min, UInt32 max)
Definition: DICOMSeries.cs:265