Fraxinus  18.10
An IGT application
cxDicomConverter.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 "cxDicomConverter.h"
13 #include "ctkDICOMDatabase.h"
14 #include "cxTypeConversions.h"
15 #include "cxTime.h"
16 #include "vtkImageData.h"
18 #include <vtkImageAppend.h>
19 #include <vtkImageCast.h>
20 #include "cxReporter.h"
21 
22 #include "cxLogger.h"
23 #include "ctkDICOMItem.h"
24 #include "dcfilefo.h" // DcmFileFormat
25 #include "dcdeftag.h" // defines all dcm tags
26 #include "dcmimage.h"
27 #include <string.h>
28 
29 #include "cxDicomImageReader.h"
30 
31 typedef vtkSmartPointer<vtkImageAppend> vtkImageAppendPtr;
32 
33 namespace cx
34 {
35 
37 {
38 }
39 
41 {
42 }
43 
44 void DicomConverter::setDicomDatabase(ctkDICOMDatabase* database)
45 {
46  mDatabase = database;
47 }
48 
49 QString DicomConverter::generateUid(DicomImageReaderPtr reader)
50 {
51  QString seriesDescription = reader->item()->GetElementAsString(DCM_SeriesDescription);
52  QString seriesNumber = reader->item()->GetElementAsString(DCM_SeriesNumber);
53 
54  // uid: uid _ <timestamp>
55  // name: find something from series
56  QString currentTimestamp = QDateTime::currentDateTime().toString(timestampSecondsFormat());
57  QString uid = QString("%1_%2_%3").arg(seriesDescription).arg(seriesNumber).arg(currentTimestamp);
58  uid = this->convertToValidName(uid);
59  return uid;
60 }
61 
62 QString DicomConverter::convertToValidName(QString text) const
63 {
64  QStringList illegal;
65  illegal << "\\s" << "\\." << ":" << ";" << "\\<" << "\\>" << "\\*"
66  << "\\^" << "," << "\\%";
67  QRegExp regexp(QString("(%1)").arg(illegal.join("|")));
68  text = text.replace(regexp, "_");
69  return text ;
70 }
71 
72 
73 QString DicomConverter::generateName(DicomImageReaderPtr reader)
74 {
75  QString seriesDescription = reader->item()->GetElementAsString(DCM_SeriesDescription);
76  QString name = convertToValidName(seriesDescription);
77  return name;
78 }
79 
80 ImagePtr DicomConverter::createCxImageFromDicomFile(QString filename, bool ignoreLocalizerImages)
81 {
83  if (!reader)
84  {
85  reportWarning(QString("File not found: %1").arg(filename));
86  return ImagePtr();
87  }
88 
89  if(ignoreLocalizerImages && reader->isLocalizerImage())
90  {
91  reportWarning(QString("Localizer image removed from series: %1").arg(filename));
92  return ImagePtr();
93  }
94 
95  if (reader->getNumberOfFrames()==0)
96  {
97  reportWarning(QString("Found no images in %1, skipping.").arg(filename));
98  return ImagePtr();
99  }
100 
101  QString uid = this->generateUid(reader);
102  QString name = this->generateName(reader);
103  cx::ImagePtr image = cx::Image::create(uid, name);
104 
105  vtkImageDataPtr imageData = reader->createVtkImageData();
106  if (!imageData)
107  {
108  reportWarning(QString("Failed to create image for %1.").arg(filename));
109  return ImagePtr();
110  }
111  image->setVtkImageData(imageData);
112 
113  QString modality = reader->item()->GetElementAsString(DCM_Modality);
114  image->setModality(modality);
115 
116  DicomImageReader::WindowLevel windowLevel = reader->getWindowLevel();
117  image->setInitialWindowLevel(windowLevel.width, windowLevel.center);
118 
119  Transform3D M = reader->getImageTransformPatient();
120  image->get_rMd_History()->setRegistration(M);
121 
122  reportDebug(QString("Image created from %1").arg(filename));
123  return image;
124 }
125 
126 std::vector<ImagePtr> DicomConverter::createImages(QStringList files)
127 {
128  std::vector<ImagePtr> retval;
129  for (int i=0; i<files.size(); ++i)
130  {
131  bool ignoreSpesialImages = true;
132  ImagePtr image = this->createCxImageFromDicomFile(files[i], ignoreSpesialImages);
133  if (image)
134  retval.push_back(image);
135  }
136  return retval;
137 }
138 
139 std::map<double, ImagePtr> DicomConverter::sortImagesAlongDirection(std::vector<ImagePtr> images, Vector3D e_sort)
140 {
141  std::map<double, ImagePtr> sorted;
142  for (int i=0; i<images.size(); ++i)
143  {
144  Vector3D pos = images[i]->get_rMd().coord(Vector3D(0,0,0));
145  double dist = dot(pos, e_sort);
146 
147  sorted[dist] = images[i];
148  }
149  return sorted;
150 }
151 
152 bool DicomConverter::slicesFormRegularGrid(std::map<double, ImagePtr> sorted, Vector3D e_sort) const
153 {
154  std::vector<Vector3D> positions;
155  std::vector<double> distances;
156  for (std::map<double, ImagePtr>::iterator iter=sorted.begin(); iter!=sorted.end(); ++iter)
157  {
158  ImagePtr current = iter->second;
159 
160  Vector3D pos = current->get_rMd().coord(Vector3D(0,0,0));
161  positions.push_back(pos);
162 
163  if (positions.size()>=2)
164  {
165  Vector3D p0 = positions[positions.size()-2];
166  Vector3D p1 = positions[positions.size()-1];
167  double dist = dot(p1-p0, e_sort);
168  distances.push_back(dist);
169 
170  Vector3D tilt = cross(p1-p0, e_sort);
171  double sliceGantryTiltTolerance = 0.001;
172  if (!similar(tilt.length(), 0.0, sliceGantryTiltTolerance))
173  {
174  reportError(QString("Dicom convert: found gantry tilt: %1, cannot create image.").arg(tilt.length()));
175  return false;
176  }
177  }
178 
179  if (distances.size()>=2)
180  {
181  double d0 = distances[distances.size()-2];
182  double d1 = distances[distances.size()-1];
183  double sliceSpacingTolerance = 0.01;
184  if (!similar(d0, d1, sliceSpacingTolerance))
185  {
186  reportError(QString("Dicom convert: found uneven slice spacing: %1 != %2, cannot create image.").arg(d0).arg(d1));
187  return false;
188  }
189  }
190  }
191 
192  return true;
193 }
194 
195 double DicomConverter::getMeanSliceDistance(std::map<double, ImagePtr> sorted) const
196 {
197  if (sorted.size()==0)
198  return 0;
199 
200  // check for multislice image
201  vtkImageDataPtr first = sorted.begin()->second->getBaseVtkImageData();
202  if (first->GetDimensions()[2]>1)
203  return first->GetSpacing()[2];
204 
205  if (sorted.size()<2)
206  return 0;
207 
208  // use average of all slices
209  double zValueLastImage = sorted.rbegin()->first;
210  double zValueFirstImage = sorted.begin()->first;
211  unsigned long numHolesBetweenImages = sorted.size() - 1;
212  return (zValueLastImage-zValueFirstImage)/numHolesBetweenImages;
213 }
214 
215 ImagePtr DicomConverter::mergeSlices(std::map<double, ImagePtr> sorted) const
216 {
217  vtkImageAppendPtr appender = vtkImageAppendPtr::New();
218  appender->SetAppendAxis(2);
219 
220  ImagePtr retval = sorted.begin()->second;
221 
222  int i = 0;
223 
224  for (std::map<double, ImagePtr>::iterator iter=sorted.begin(); iter!=sorted.end(); ++iter)
225  {
226  ImagePtr current = iter->second;
227 
228  // Set window width and level to the values of the middle frame
229  if (i == sorted.size() / 2)
230  retval->setInitialWindowLevel(current->getInitialWindowWidth(), current->getInitialWindowLevel());
231  ++i;
232 
233  //Convert all slices to same format
234  vtkImageCastPtr imageCast = vtkImageCastPtr::New();
235  imageCast->SetInputData(current->getBaseVtkImageData());
236  imageCast->SetOutputScalarTypeToShort();
237  imageCast->Update();
238 
239  appender->AddInputData(imageCast->GetOutput());
240  }
241  appender->Update();
242 
243  vtkImageDataPtr wholeImage = appender->GetOutput();
244  Eigen::Array3d spacing(wholeImage->GetSpacing());
245  spacing[2] = this->getMeanSliceDistance(sorted);
246  wholeImage->SetSpacing(spacing.data());
247 
248  retval->setVtkImageData(wholeImage);
249 
250  return retval;
251 }
252 
254 {
255  QStringList files = mDatabase->filesForSeries(series);
256 
257  std::vector<ImagePtr> images = this->createImages(files);
258 
259  if (images.empty())
260  return ImagePtr();
261 
262  if (images.size()==1)
263  {
264  return images.front();
265  }
266 
267  Vector3D e_sort = images.front()->get_rMd().vector(Vector3D(0,0,1));
268 
269  std::map<double, ImagePtr> sorted = this->sortImagesAlongDirection(images, e_sort);
270 
271  if (!this->slicesFormRegularGrid(sorted, e_sort))
272  return ImagePtr();
273 
274  ImagePtr retval = this->mergeSlices(sorted);
275  return retval;
276 }
277 
278 } /* namespace cx */
static DicomImageReaderPtr createFromFile(QString filename)
void reportError(QString msg)
Definition: cxLogger.cpp:71
static ImagePtr create(const QString &uid, const QString &name)
Definition: cxImage.cpp:97
Transform3D Transform3D
Transform3D is a representation of an affine 3D transform.
boost::shared_ptr< class Image > ImagePtr
Definition: cxDicomWidget.h:27
void setDicomDatabase(ctkDICOMDatabase *database)
QString timestampSecondsFormat()
Definition: cxTime.cpp:18
Vector3D cross(const Vector3D &a, const Vector3D &b)
compute cross product of a and b.
Definition: cxVector3D.cpp:41
ImagePtr convertToImage(QString seriesUid)
void reportWarning(QString msg)
Definition: cxLogger.cpp:70
double dot(const Vector3D &a, const Vector3D &b)
compute inner product (or dot product) of a and b.
Definition: cxVector3D.cpp:46
vtkSmartPointer< class vtkImageCast > vtkImageCastPtr
boost::shared_ptr< class DicomImageReader > DicomImageReaderPtr
Eigen::Vector3d Vector3D
Vector3D is a representation of a point or vector in 3D.
Definition: cxVector3D.h:42
bool similar(const CameraInfo &lhs, const CameraInfo &rhs, double tol)
vtkSmartPointer< class vtkImageData > vtkImageDataPtr
void reportDebug(QString msg)
Definition: cxLogger.cpp:68
vtkSmartPointer< vtkImageAppend > vtkImageAppendPtr
Namespace for all CustusX production code.