CustusX  18.04
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 p1 = sorted.rbegin()->first;
210  double p0 = sorted.begin()->first;
211  return (p1-p0)/sorted.size();
212 }
213 
214 ImagePtr DicomConverter::mergeSlices(std::map<double, ImagePtr> sorted) const
215 {
216  vtkImageAppendPtr appender = vtkImageAppendPtr::New();
217  appender->SetAppendAxis(2);
218 
219  ImagePtr retval = sorted.begin()->second;
220 
221  int i = 0;
222 
223  for (std::map<double, ImagePtr>::iterator iter=sorted.begin(); iter!=sorted.end(); ++iter)
224  {
225  ImagePtr current = iter->second;
226 
227  // Set window width and level to the values of the middle frame
228  if (i == sorted.size() / 2)
229  retval->setInitialWindowLevel(current->getInitialWindowWidth(), current->getInitialWindowLevel());
230  ++i;
231 
232  //Convert all slices to same format
233  vtkImageCastPtr imageCast = vtkImageCastPtr::New();
234  imageCast->SetInputData(current->getBaseVtkImageData());
235  imageCast->SetOutputScalarTypeToShort();
236  imageCast->Update();
237 
238  appender->AddInputData(imageCast->GetOutput());
239  }
240  appender->Update();
241 
242  vtkImageDataPtr wholeImage = appender->GetOutput();
243  Eigen::Array3d spacing(wholeImage->GetSpacing());
244  spacing[2] = this->getMeanSliceDistance(sorted);
245  wholeImage->SetSpacing(spacing.data());
246 
247  retval->setVtkImageData(wholeImage);
248 
249  return retval;
250 }
251 
253 {
254  QStringList files = mDatabase->filesForSeries(series);
255 
256  std::vector<ImagePtr> images = this->createImages(files);
257 
258  if (images.empty())
259  return ImagePtr();
260 
261  if (images.size()==1)
262  {
263  return images.front();
264  }
265 
266  Vector3D e_sort = images.front()->get_rMd().vector(Vector3D(0,0,1));
267 
268  std::map<double, ImagePtr> sorted = this->sortImagesAlongDirection(images, e_sort);
269 
270  if (!this->slicesFormRegularGrid(sorted, e_sort))
271  return ImagePtr();
272 
273  ImagePtr retval = this->mergeSlices(sorted);
274  return retval;
275 }
276 
277 } /* 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.