Fraxinus  17.12
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) 2008-2014, SINTEF Department of Medical Technology
5 All rights reserved.
6 
7 Redistribution and use in source and binary forms, with or without
8 modification, are permitted provided that the following conditions are met:
9 
10 1. Redistributions of source code must retain the above copyright notice,
11  this list of conditions and the following disclaimer.
12 
13 2. Redistributions in binary form must reproduce the above copyright notice,
14  this list of conditions and the following disclaimer in the documentation
15  and/or other materials provided with the distribution.
16 
17 3. Neither the name of the copyright holder nor the names of its contributors
18  may be used to endorse or promote products derived from this software
19  without specific prior written permission.
20 
21 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
25 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29 OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 =========================================================================*/
32 
33 #include "cxDicomConverter.h"
34 #include "ctkDICOMDatabase.h"
35 #include "cxTypeConversions.h"
36 #include "cxTime.h"
37 #include "vtkImageData.h"
39 #include <vtkImageAppend.h>
40 #include <vtkImageCast.h>
41 #include "cxReporter.h"
42 
43 #include "cxLogger.h"
44 #include "ctkDICOMItem.h"
45 #include "dcfilefo.h" // DcmFileFormat
46 #include "dcdeftag.h" // defines all dcm tags
47 #include "dcmimage.h"
48 #include <string.h>
49 
50 #include "cxDicomImageReader.h"
51 
52 typedef vtkSmartPointer<vtkImageAppend> vtkImageAppendPtr;
53 
54 namespace cx
55 {
56 
58 {
59 }
60 
62 {
63 }
64 
65 void DicomConverter::setDicomDatabase(ctkDICOMDatabase* database)
66 {
67  mDatabase = database;
68 }
69 
70 QString DicomConverter::generateUid(DicomImageReaderPtr reader)
71 {
72  QString seriesDescription = reader->item()->GetElementAsString(DCM_SeriesDescription);
73  QString seriesNumber = reader->item()->GetElementAsString(DCM_SeriesNumber);
74 
75  // uid: uid _ <timestamp>
76  // name: find something from series
77  QString currentTimestamp = QDateTime::currentDateTime().toString(timestampSecondsFormat());
78  QString uid = QString("%1_%2_%3").arg(seriesDescription).arg(seriesNumber).arg(currentTimestamp);
79  uid = this->convertToValidName(uid);
80  return uid;
81 }
82 
83 QString DicomConverter::convertToValidName(QString text) const
84 {
85  QStringList illegal;
86  illegal << "\\s" << "\\." << ":" << ";" << "\\<" << "\\>" << "\\*"
87  << "\\^" << "," << "\\%";
88  QRegExp regexp(QString("(%1)").arg(illegal.join("|")));
89  text = text.replace(regexp, "_");
90  return text ;
91 }
92 
93 
94 QString DicomConverter::generateName(DicomImageReaderPtr reader)
95 {
96  QString seriesDescription = reader->item()->GetElementAsString(DCM_SeriesDescription);
97  QString name = convertToValidName(seriesDescription);
98  return name;
99 }
100 
101 ImagePtr DicomConverter::createCxImageFromDicomFile(QString filename, bool ignoreLocalizerImages)
102 {
104  if (!reader)
105  {
106  reportWarning(QString("File not found: %1").arg(filename));
107  return ImagePtr();
108  }
109 
110  if(ignoreLocalizerImages && reader->isLocalizerImage())
111  {
112  reportWarning(QString("Localizer image removed from series: %1").arg(filename));
113  return ImagePtr();
114  }
115 
116  if (reader->getNumberOfFrames()==0)
117  {
118  reportWarning(QString("Found no images in %1, skipping.").arg(filename));
119  return ImagePtr();
120  }
121 
122  QString uid = this->generateUid(reader);
123  QString name = this->generateName(reader);
124  cx::ImagePtr image = cx::Image::create(uid, name);
125 
126  vtkImageDataPtr imageData = reader->createVtkImageData();
127  if (!imageData)
128  {
129  reportWarning(QString("Failed to create image for %1.").arg(filename));
130  return ImagePtr();
131  }
132  image->setVtkImageData(imageData);
133 
134  QString modality = reader->item()->GetElementAsString(DCM_Modality);
135  image->setModality(modality);
136 
137  DicomImageReader::WindowLevel windowLevel = reader->getWindowLevel();
138  image->setInitialWindowLevel(windowLevel.width, windowLevel.center);
139 
140  Transform3D M = reader->getImageTransformPatient();
141  image->get_rMd_History()->setRegistration(M);
142 
143  reportDebug(QString("Image created from %1").arg(filename));
144  return image;
145 }
146 
147 std::vector<ImagePtr> DicomConverter::createImages(QStringList files)
148 {
149  std::vector<ImagePtr> retval;
150  for (int i=0; i<files.size(); ++i)
151  {
152  bool ignoreSpesialImages = true;
153  ImagePtr image = this->createCxImageFromDicomFile(files[i], ignoreSpesialImages);
154  if (image)
155  retval.push_back(image);
156  }
157  return retval;
158 }
159 
160 std::map<double, ImagePtr> DicomConverter::sortImagesAlongDirection(std::vector<ImagePtr> images, Vector3D e_sort)
161 {
162  std::map<double, ImagePtr> sorted;
163  for (int i=0; i<images.size(); ++i)
164  {
165  Vector3D pos = images[i]->get_rMd().coord(Vector3D(0,0,0));
166  double dist = dot(pos, e_sort);
167 
168  sorted[dist] = images[i];
169  }
170  return sorted;
171 }
172 
173 bool DicomConverter::slicesFormRegularGrid(std::map<double, ImagePtr> sorted, Vector3D e_sort) const
174 {
175  std::vector<Vector3D> positions;
176  std::vector<double> distances;
177  for (std::map<double, ImagePtr>::iterator iter=sorted.begin(); iter!=sorted.end(); ++iter)
178  {
179  ImagePtr current = iter->second;
180 
181  Vector3D pos = current->get_rMd().coord(Vector3D(0,0,0));
182  positions.push_back(pos);
183 
184  if (positions.size()>=2)
185  {
186  Vector3D p0 = positions[positions.size()-2];
187  Vector3D p1 = positions[positions.size()-1];
188  double dist = dot(p1-p0, e_sort);
189  distances.push_back(dist);
190 
191  Vector3D tilt = cross(p1-p0, e_sort);
192  double sliceGantryTiltTolerance = 0.001;
193  if (!similar(tilt.length(), 0.0, sliceGantryTiltTolerance))
194  {
195  reportError(QString("Dicom convert: found gantry tilt: %1, cannot create image.").arg(tilt.length()));
196  return false;
197  }
198  }
199 
200  if (distances.size()>=2)
201  {
202  double d0 = distances[distances.size()-2];
203  double d1 = distances[distances.size()-1];
204  double sliceSpacingTolerance = 0.01;
205  if (!similar(d0, d1, sliceSpacingTolerance))
206  {
207  reportError(QString("Dicom convert: found uneven slice spacing: %1 != %2, cannot create image.").arg(d0).arg(d1));
208  return false;
209  }
210  }
211  }
212 
213  return true;
214 }
215 
216 double DicomConverter::getMeanSliceDistance(std::map<double, ImagePtr> sorted) const
217 {
218  if (sorted.size()==0)
219  return 0;
220 
221  // check for multislice image
222  vtkImageDataPtr first = sorted.begin()->second->getBaseVtkImageData();
223  if (first->GetDimensions()[2]>1)
224  return first->GetSpacing()[2];
225 
226  if (sorted.size()<2)
227  return 0;
228 
229  // use average of all slices
230  double p1 = sorted.rbegin()->first;
231  double p0 = sorted.begin()->first;
232  return (p1-p0)/sorted.size();
233 }
234 
235 ImagePtr DicomConverter::mergeSlices(std::map<double, ImagePtr> sorted) const
236 {
237  vtkImageAppendPtr appender = vtkImageAppendPtr::New();
238  appender->SetAppendAxis(2);
239 
240  ImagePtr retval = sorted.begin()->second;
241 
242  int i = 0;
243 
244  for (std::map<double, ImagePtr>::iterator iter=sorted.begin(); iter!=sorted.end(); ++iter)
245  {
246  ImagePtr current = iter->second;
247 
248  // Set window width and level to the values of the middle frame
249  if (i == sorted.size() / 2)
250  retval->setInitialWindowLevel(current->getInitialWindowWidth(), current->getInitialWindowLevel());
251  ++i;
252 
253  //Convert all slices to same format
254  vtkImageCastPtr imageCast = vtkImageCastPtr::New();
255  imageCast->SetInputData(current->getBaseVtkImageData());
256  imageCast->SetOutputScalarTypeToShort();
257  imageCast->Update();
258 
259  appender->AddInputData(imageCast->GetOutput());
260  }
261  appender->Update();
262 
263  vtkImageDataPtr wholeImage = appender->GetOutput();
264  Eigen::Array3d spacing(wholeImage->GetSpacing());
265  spacing[2] = this->getMeanSliceDistance(sorted);
266  wholeImage->SetSpacing(spacing.data());
267 
268  retval->setVtkImageData(wholeImage);
269 
270  return retval;
271 }
272 
274 {
275  QStringList files = mDatabase->filesForSeries(series);
276 
277  std::vector<ImagePtr> images = this->createImages(files);
278 
279  if (images.empty())
280  return ImagePtr();
281 
282  if (images.size()==1)
283  {
284  return images.front();
285  }
286 
287  Vector3D e_sort = images.front()->get_rMd().vector(Vector3D(0,0,1));
288 
289  std::map<double, ImagePtr> sorted = this->sortImagesAlongDirection(images, e_sort);
290 
291  if (!this->slicesFormRegularGrid(sorted, e_sort))
292  return ImagePtr();
293 
294  ImagePtr retval = this->mergeSlices(sorted);
295  return retval;
296 }
297 
298 } /* namespace cx */
static DicomImageReaderPtr createFromFile(QString filename)
void reportError(QString msg)
Definition: cxLogger.cpp:92
static ImagePtr create(const QString &uid, const QString &name)
Definition: cxImage.cpp:118
Transform3D Transform3D
Transform3D is a representation of an affine 3D transform.
boost::shared_ptr< class Image > ImagePtr
Definition: cxDicomWidget.h:48
void setDicomDatabase(ctkDICOMDatabase *database)
QString timestampSecondsFormat()
Definition: cxTime.cpp:39
Vector3D cross(const Vector3D &a, const Vector3D &b)
compute cross product of a and b.
Definition: cxVector3D.cpp:62
ImagePtr convertToImage(QString seriesUid)
void reportWarning(QString msg)
Definition: cxLogger.cpp:91
double dot(const Vector3D &a, const Vector3D &b)
compute inner product (or dot product) of a and b.
Definition: cxVector3D.cpp:67
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:63
bool similar(const CameraInfo &lhs, const CameraInfo &rhs, double tol)
vtkSmartPointer< class vtkImageData > vtkImageDataPtr
void reportDebug(QString msg)
Definition: cxLogger.cpp:89
vtkSmartPointer< vtkImageAppend > vtkImageAppendPtr
Namespace for all CustusX production code.