CustusX  18.04-rc6
An IGT application
cxDicomImageReader.cpp
Go to the documentation of this file.
1 /*=========================================================================
2 This file is part of CustusX, an Image Guided Therapy Application.
3 
4 Copyright (c) SINTEF Department of Medical Technology.
5 All rights reserved.
6 
7 CustusX is released under a BSD 3-Clause license.
8 
9 See Lisence.txt (https://github.com/SINTEFMedtek/CustusX/blob/master/License.txt) for details.
10 =========================================================================*/
11 
12 #include "cxDicomImageReader.h"
13 
14 #include "cxVolumeHelpers.h"
15 #include "dcvrpn.h"
16 #include "cxLogger.h"
17 
18 namespace cx
19 {
20 
22 {
24  if (retval->loadFile(filename))
25  return retval;
26  else
27  return DicomImageReaderPtr();
28 }
29 
30 DicomImageReader::DicomImageReader() :
31  mDataset(NULL)
32 {
33 }
34 
35 bool DicomImageReader::loadFile(QString filename)
36 {
37  mFilename = filename;
38  OFCondition status = mFileFormat.loadFile(filename.toLatin1().data());
39  if( !status.good() )
40  {
41  return false;
42  }
43 
44  mDataset = mFileFormat.getDataset();
45  return true;
46 }
47 
49 {
50  return this->wrapInCTK(mDataset);
51 }
52 
53 double DicomImageReader::getDouble(const DcmTagKey& tag, const unsigned long pos, const OFBool searchIntoSub) const
54 {
55 // return this->getDouble(mDataset, tag, pos, searchIntoSub);
56  double retval = 0;
57  OFCondition condition;
58  condition = mDataset->findAndGetFloat64(tag, retval, pos, searchIntoSub);
59  if (!condition.good())
60  {
61  QString tagName = this->item()->TagDescription(tag);
62  this->error(QString("Failed to get tag %1/%2").arg(tagName).arg(pos));
63  }
64  return retval;
65 }
66 //double DicomImageReader::getDouble(DcmObject* dcmObject, const DcmTagKey& tag, const unsigned long pos, const OFBool searchIntoSub) const
67 //{
68 // DcmStack stack;
69 // dcmObject->search(tag, stack);
70 
71 // DcmElement* element = dynamic_cast<DcmElement*>(stack.top());
72 
73 // if(!element)
74 // {
75 // QString tagName = this->item()->TagDescription(tag);
76 // this->error(QString("Failed to get DcmAttributeTag with tag %1/%2").arg(tagName).arg(pos));
77 // return 0;
78 // }
79 // else
80 // {
81 // double val;
82 // element->getFloat64(val);
83 // }
84 
85 // double retval = 0;
86 // OFCondition condition;
87 // condition = element->getFloat64(retval, pos);
88 // if (!condition.good())
89 // {
90 // QString tagName = this->item()->TagDescription(tag);
91 // this->error(QString("Failed to get tag %1/%2").arg(tagName).arg(pos));
92 // }
93 // return retval;
94 //}
95 
97 {
98  WindowLevel retval;
99  retval.center = this->getDouble(DCM_WindowCenter, 0, OFTrue);
100  retval.width = this->getDouble(DCM_WindowWidth, 0, OFTrue);
101  return retval;
102 }
103 
105 {
106  //DICOM standard PS3.3 section C.7.6.1.1.2 Image Type
107  //http://dicom.nema.org/medical/dicom/current/output/html/part03.html#sect_C.7.6.1.1.2
108  bool retval = false;
109 
110  OFCondition condition;
111  OFString value;
112  condition = mDataset->findAndGetOFString(DCM_ImageType, value, 2, OFTrue);
113  if (condition.good())
114  {
115  QString imageSpesialization(value.c_str());
116  if (imageSpesialization.compare("LOCALIZER", Qt::CaseSensitive) == 0)
117  retval = true;
118  }
119  return retval;
120 }
121 
123 {
124  int numberOfFrames = this->item()->GetElementAsInteger(DCM_NumberOfFrames);
125  if (numberOfFrames==0)
126  {
127  unsigned short rows = 0;
128  unsigned short columns = 0;
129  mDataset->findAndGetUint16(DCM_Rows, rows, 0, OFTrue);
130  mDataset->findAndGetUint16(DCM_Columns, columns, 0, OFTrue);
131  if (rows*columns > 0)
132  numberOfFrames = 1; // seems like we have a 2D image
133  }
134  return numberOfFrames;
135 }
136 
138 {
139  Vector3D pos;
140  Vector3D e_x;
141  Vector3D e_y;
142 
143  for (int i=0; i<3; ++i)
144  {
145  e_x[i] = this->getDouble(DCM_ImageOrientationPatient, i, OFTrue);
146  e_y[i] = this->getDouble(DCM_ImageOrientationPatient, i+3, OFTrue);
147  pos[i] = this->getDouble(DCM_ImagePositionPatient, i, OFTrue);
148  }
149 
150  Vector3D zero_vec(0,0,0);
151  if( similar(e_x,zero_vec) && similar(e_y,zero_vec)) // Zero matrix
152  {
153  report("Set transform matrix to identity");
154  e_x[0]=1;
155  e_y[1]=1;
156  }
157 
158  Transform3D retval = cx::createTransformIJC(e_x, e_y, pos);
159  return retval;
160 }
161 ctkDICOMItemPtr DicomImageReader::wrapInCTK(DcmItem* item) const
162 {
163  if (!item)
164  return ctkDICOMItemPtr();
165  ctkDICOMItemPtr retval(new ctkDICOMItem);
166  retval->InitializeFromItem(item);
167  return retval;
168 }
169 
170 void DicomImageReader::error(QString message) const
171 {
172  reportError(QString("Dicom convert: [%1] in %2").arg(message).arg(mFilename));
173 }
174 
176 {
177  //TODO: Use DicomImage::createMonochromeImage() to get a monochrome copy for convenience
178 
179  DicomImage dicomImage(mFilename.toLatin1().data()); //, CIF_MayDetachPixelData );
180  const DiPixel *pixels = dicomImage.getInterData();
181  if (!pixels)
182  {
183  this->error("Found no pixel data");
184  return vtkImageDataPtr();
185  }
186 
187  vtkImageDataPtr data = vtkImageDataPtr::New();
188 
189  data->SetSpacing(this->getSpacing().data());
190 
191  Eigen::Array3i dim = this->getDim(dicomImage);
192  data->SetExtent(0, dim[0]-1, 0, dim[1]-1, 0, dim[2]-1);
193 
194  int samplesPerPixel = pixels->getPlanes();
195  int scalarSize = dim.prod() * samplesPerPixel;
196  int pixelDepth = dicomImage.getDepth();
197 
198  switch (pixels->getRepresentation())
199  {
200  case EPR_Uint8:
201 // std::cout << " VTK_UNSIGNED_CHAR" << std::endl;
202  data->AllocateScalars(VTK_UNSIGNED_CHAR, samplesPerPixel);
203  break;
204  case EPR_Uint16:
205 // std::cout << " VTK_UNSIGNED_SHORT" << std::endl;
206  data->AllocateScalars(VTK_UNSIGNED_SHORT, samplesPerPixel);
207  break;
208  case EPR_Uint32:
209 // std::cout << " VTK_UNSIGNED_INT" << std::endl;
210  data->AllocateScalars(VTK_UNSIGNED_INT, samplesPerPixel);
211  break;
212  case EPR_Sint8:
213 // std::cout << " VTK_CHAR" << std::endl;
214  data->AllocateScalars(VTK_CHAR, samplesPerPixel);
215  break;
216  case EPR_Sint16:
217 // std::cout << " VTK_SHORT" << std::endl;
218  data->AllocateScalars(VTK_SHORT, samplesPerPixel);
219  break;
220  case EPR_Sint32:
221 // std::cout << " VTK_INT" << std::endl;
222  data->AllocateScalars(VTK_INT, samplesPerPixel);
223  break;
224  }
225 
226  int bytesPerPixel = data->GetScalarSize() * samplesPerPixel;
227 
228  memcpy(data->GetScalarPointer(), pixels->getData(), pixels->getCount()*bytesPerPixel);
229  if (pixels->getCount()!=scalarSize)
230  this->error("Mismatch in pixel counts");
231  setDeepModified(data);
232  return data;
233 }
234 
235 Eigen::Array3d DicomImageReader::getSpacing() const
236 {
237  Eigen::Array3d spacing;
238  spacing[0] = this->getDouble(DCM_PixelSpacing, 0, OFTrue);
239  spacing[1] = this->getDouble(DCM_PixelSpacing, 1, OFTrue);
240 
241  double sliceThickness = this->getDouble(DCM_SliceThickness, 0, OFTrue);
242  spacing[2] = sliceThickness;
243 
244  if(this->isMultiFrameImage())
245  {
246  double sliceSpacing = this->getSliceSpacing();
247  if(similar(sliceSpacing, 0))
248  CX_LOG_WARNING() << "Cannot get slice spacing. Using slice thickness instead: " << sliceThickness;
249  else
250  spacing[2] = sliceSpacing;
251  }
252 
253 // double spacingBetweenSlices = this->getDouble(DCM_SpacingBetweenSlices, 0, OFTrue);//Usually only for MR
254 // std::cout << "DCM_SpacingBetweenSlices: " << spacingBetweenSlices << std::endl;
255 
256 // std::cout << " spacing: " << spacing << std::endl;
257  return spacing;
258 }
259 
260 bool DicomImageReader::isMultiFrameImage() const
261 {
262  //For now, just use number of z positions as indicator
263  QVector<double> zPos = this->getZPositions();
264  if(zPos.size() < 2)
265  return false;
266  return true;
267 }
268 
269 double DicomImageReader::getSliceSpacing() const
270 {
271  double retval;
272 
273  QVector<double> zPos = this->getZPositions();
274  if(zPos.size() < 2)
275  return 0;
276  retval = zPos[1] - zPos[0];
277 
278  for(int i = 2; i < zPos.size(); ++i)
279  {
280  double dist = zPos[i] - zPos[i-1];
281  if(!similar(dist, retval))
282  CX_LOG_WARNING() << "Distance between frame: " << i << " and " << i+1 << " is: " << dist << " != " << "dist between frame 0 and 1: " << retval;
283  }
284  if(retval < 0)
285  retval = zPos[0] - zPos[1];
286  return retval;
287 }
288 
289 QVector<double> DicomImageReader::getZPositions() const
290 {
291  QVector<double> retval;
292  DcmStack cleanStack;
293  DcmElement* stackElement;
294  OFCondition condition;
295  int i = 0;
296  do
297  {
298  do
299  condition = mDataset->nextObject(cleanStack, OFTrue);
300  while(condition.good() && cleanStack.top()->getTag() != DCM_ImagePositionPatient);
301 
302  ++i;
303  if(condition.good())
304  {
305  stackElement = dynamic_cast<DcmElement*>(cleanStack.top());
306  double val;
307  condition = stackElement->getFloat64(val, 2);
308  if(condition.bad())
309  {
310  CX_LOG_WARNING() << "Cannot get z pos for frame " << i;
311  return retval;
312  }
313  retval << val;
314 // CX_LOG_DEBUG() << "frame " << i << " z pos: " << val;
315  }
316  }
317  while(condition.good());
318  return retval;
319 }
320 
321 //Transform3D DicomImageReader::getTransform(DcmItem* dcmItem)
322 //{
323 
324 // e_x[i] = this->getDouble(DCM_ImageOrientationPatient, i, OFTrue);
325 // e_y[i] = this->getDouble(DCM_ImageOrientationPatient, i+3, OFTrue);
326 // pos[i] = this->getDouble(DCM_ImagePositionPatient, i, OFTrue);
327 
328 
329 // condition = stackElement->findAndGetOFString(DCM_ImagePositionPatient, value, 2, OFTrue);
330 //}
331 
332 Eigen::Array3i DicomImageReader::getDim(const DicomImage& dicomImage) const
333 {
334  Eigen::Array3i dim;
335  dim[0] = dicomImage.getWidth();
336  dim[1] = dicomImage.getHeight();
337  dim[2] = dicomImage.getFrameCount();
338  return dim;
339 }
340 
342 {
343  QString rawName = this->item()->GetElementAsString(DCM_PatientName);
344  return this->formatPatientName(rawName);
345 }
346 
347 QString DicomImageReader::formatPatientName(QString rawName) const
348 {
349  // ripped from ctkDICOMModel
350 
351  OFString dicomName = rawName.toStdString().c_str();
352  OFString formattedName;
353  OFString lastName, firstName, middleName, namePrefix, nameSuffix;
354  OFCondition l_error = DcmPersonName::getNameComponentsFromString(dicomName,
355  lastName, firstName, middleName, namePrefix, nameSuffix);
356  if (l_error.good())
357  {
358  formattedName.clear();
359  /* concatenate name components per this convention
360  * Last, First Middle, Suffix (Prefix)
361  * */
362  if (!lastName.empty())
363  {
364  formattedName += lastName;
365  if ( !(firstName.empty() && middleName.empty()) )
366  {
367  formattedName += ",";
368  }
369  }
370  if (!firstName.empty())
371  {
372  formattedName += " ";
373  formattedName += firstName;
374  }
375  if (!middleName.empty())
376  {
377  formattedName += " ";
378  formattedName += middleName;
379  }
380  if (!nameSuffix.empty())
381  {
382  formattedName += ", ";
383  formattedName += nameSuffix;
384  }
385  if (!namePrefix.empty())
386  {
387  formattedName += " (";
388  formattedName += namePrefix;
389  formattedName += ")";
390  }
391  }
392  return QString(formattedName.c_str());
393 }
394 
395 
396 } // namespace cx
397 
static DicomImageReaderPtr createFromFile(QString filename)
void reportError(QString msg)
Definition: cxLogger.cpp:71
Transform3D Transform3D
Transform3D is a representation of an affine 3D transform.
WindowLevel getWindowLevel() const
ctkDICOMItemPtr item() const
Transform3D getImageTransformPatient() const
vtkImageDataPtr createVtkImageData()
Transform3D createTransformIJC(const Vector3D &ivec, const Vector3D &jvec, const Vector3D &center)
QString getPatientName() const
boost::shared_ptr< class ctkDICOMItem > ctkDICOMItemPtr
boost::shared_ptr< class DicomImageReader > DicomImageReaderPtr
Eigen::Vector3d Vector3D
Vector3D is a representation of a point or vector in 3D.
Definition: cxVector3D.h:42
void report(QString msg)
Definition: cxLogger.cpp:69
void setDeepModified(vtkImageDataPtr image)
bool similar(const CameraInfo &lhs, const CameraInfo &rhs, double tol)
#define CX_LOG_WARNING
Definition: cxLogger.h:98
vtkSmartPointer< class vtkImageData > vtkImageDataPtr
Namespace for all CustusX production code.