CustusX  2023.01.05-dev+develop.0da12
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 #include "cxCustomMetaImage.h"
31 
32 typedef vtkSmartPointer<vtkImageAppend> vtkImageAppendPtr;
33 
34 namespace cx
35 {
36 
38 {
39 }
40 
42 {
43 }
44 
45 void DicomConverter::setDicomDatabase(ctkDICOMDatabase* database)
46 {
47  mDatabase = database;
48 }
49 
50 QString DicomConverter::generateUid(DicomImageReaderPtr reader)
51 {
52  QString seriesDescription = reader->item()->GetElementAsString(DCM_SeriesDescription);
53  QString seriesNumber = reader->item()->GetElementAsString(DCM_SeriesNumber);
54 
55  // uid: uid _ <timestamp>
56  // name: find something from series
57  QString currentTimestamp = QDateTime::currentDateTime().toString(timestampSecondsFormat());
58  QString uid = QString("%1_%2_%3").arg(seriesDescription).arg(seriesNumber).arg(currentTimestamp);
59  uid = this->convertToValidName(uid);
60  return uid;
61 }
62 
63 QString DicomConverter::convertToValidName(QString text) const
64 {
65  QStringList illegal;
66  illegal << "\\s" << "\\." << ":" << ";" << "\\<" << "\\>" << "\\*"
67  << "\\^" << "," << "\\%";
68  QRegExp regexp(QString("(%1)").arg(illegal.join("|")));
69  text = text.replace(regexp, "_");
70  return text ;
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 QString DicomConverter::getSeriesNumber(DicomImageReaderPtr reader)
81 {
82  QString seriesNumber = reader->item()->GetElementAsString(DCM_SeriesNumber);
83  return seriesNumber;
84 }
85 
86 ImagePtr DicomConverter::createCxImageFromDicomFile(QString filename, bool ignoreLocalizerImages)
87 {
89  if (!reader)
90  {
91  reportWarning(QString("File not found: %1").arg(filename));
92  return ImagePtr();
93  }
94 
95  if(ignoreLocalizerImages && reader->isLocalizerImage())
96  {
97  reportWarning(QString("Localizer image removed from series: %1").arg(filename));
98  return ImagePtr();
99  }
100 
101  if (reader->getNumberOfFrames()==0)
102  {
103  reportWarning(QString("Found no images in %1, skipping.").arg(filename));
104  return ImagePtr();
105  }
106 
107  QString uid = this->generateUid(reader);
108  QString name = this->generateName(reader);
109  cx::ImagePtr image = cx::Image::create(uid, name);
110  image->setDicomSeriesNumber(this->getSeriesNumber(reader));
111 
112  vtkImageDataPtr imageData = reader->createVtkImageData();
113  if (!imageData)
114  {
115  reportWarning(QString("Failed to create image for %1.").arg(filename));
116  return ImagePtr();
117  }
118  image->setVtkImageData(imageData);
119 
120  QString modalityString = reader->item()->GetElementAsString(DCM_Modality);
121  image->setModality(convertToModality(modalityString));
122 
123  image->setImageType(istEMPTY);//Setting image subtype to empty for now. DCM_ImageType (value 3, and 4) may possibly be used. Also series name often got this kind of information.
124 
125  DicomImageReader::WindowLevel windowLevel = reader->getWindowLevel();
126  image->setInitialWindowLevel(windowLevel.width, windowLevel.center);
127 
128  Transform3D M = reader->getImageTransformPatient();
129  image->get_rMd_History()->setRegistration(M);
130 
131 // reportDebug(QString("Image created from %1").arg(filename));
132  return image;
133 }
134 
135 std::vector<ImagePtr> DicomConverter::createImages(QStringList files)
136 {
137  std::vector<ImagePtr> retval;
138  for (int i=0; i<files.size(); ++i)
139  {
140  bool ignoreSpesialImages = true;
141  ImagePtr image = this->createCxImageFromDicomFile(files[i], ignoreSpesialImages);
142  if (image)
143  retval.push_back(image);
144  }
145  return retval;
146 }
147 
148 std::map<double, ImagePtr> DicomConverter::sortImagesAlongDirection(std::vector<ImagePtr> images, Vector3D e_sort)
149 {
150  std::map<double, ImagePtr> sorted;
151  for (unsigned i=0; i<images.size(); ++i)
152  {
153  Vector3D pos = images[i]->get_rMd().coord(Vector3D(0,0,0));
154  double dist = dot(pos, e_sort);
155 
156  sorted[dist] = images[i];
157  }
158  return sorted;
159 }
160 
161 bool DicomConverter::slicesFormRegularGrid(std::map<double, ImagePtr> sorted, Vector3D e_sort) const
162 {
163  std::vector<Vector3D> positions;
164  std::vector<double> distances;
165  for (std::map<double, ImagePtr>::iterator iter=sorted.begin(); iter!=sorted.end(); ++iter)
166  {
167  ImagePtr current = iter->second;
168 
169  Vector3D pos = current->get_rMd().coord(Vector3D(0,0,0));
170  positions.push_back(pos);
171 
172  if (positions.size()>=2)
173  {
174  Vector3D p0 = positions[positions.size()-2];
175  Vector3D p1 = positions[positions.size()-1];
176  double dist = dot(p1-p0, e_sort);
177  distances.push_back(dist);
178 
179  Vector3D tilt = cross(p1-p0, e_sort);
180  double sliceGantryTiltTolerance = 0.001;
181  if (!similar(tilt.length(), 0.0, sliceGantryTiltTolerance))
182  {
183  reportError(QString("Dicom convert: found gantry tilt: %1, cannot create image.").arg(tilt.length()));
184  return false;
185  }
186  }
187 
188  if (distances.size()>=2)
189  {
190  double d0 = distances[distances.size()-2];
191  double d1 = distances[distances.size()-1];
192  double sliceSpacingTolerance = 0.01;
193  if (!similar(d0, d1, sliceSpacingTolerance))
194  {
195  reportError(QString("Dicom convert: found uneven slice spacing: %1 != %2, cannot create image.").arg(d0).arg(d1));
196  return false;
197  }
198  }
199  }
200 
201  return true;
202 }
203 
204 double DicomConverter::getMeanSliceDistance(std::map<double, ImagePtr> sorted) const
205 {
206  if (sorted.size()==0)
207  return 0;
208 
209  // check for multislice image
210  vtkImageDataPtr first = sorted.begin()->second->getBaseVtkImageData();
211  if (first->GetDimensions()[2]>1)
212  return first->GetSpacing()[2];
213 
214  if (sorted.size()<2)
215  return 0;
216 
217  // use average of all slices
218  double zValueLastImage = sorted.rbegin()->first;
219  double zValueFirstImage = sorted.begin()->first;
220  unsigned long numHolesBetweenImages = sorted.size() - 1;
221  return (zValueLastImage-zValueFirstImage)/numHolesBetweenImages;
222 }
223 
224 ImagePtr DicomConverter::mergeSlices(std::map<double, ImagePtr> sorted) const
225 {
226  vtkImageAppendPtr appender = vtkImageAppendPtr::New();
227  appender->SetAppendAxis(2);
228 
229  ImagePtr retval = sorted.begin()->second;
230 
231  int i = 0;
232 
233  for (std::map<double, ImagePtr>::iterator iter=sorted.begin(); iter!=sorted.end(); ++iter)
234  {
235  ImagePtr current = iter->second;
236 
237  // Set window width and level to the values of the middle frame
238  if (i == sorted.size() / 2)
239  retval->setInitialWindowLevel(current->getInitialWindowWidth(), current->getInitialWindowLevel());
240  ++i;
241 
242  //Convert all slices to same format
243  vtkImageCastPtr imageCast = vtkImageCastPtr::New();
244  imageCast->SetInputData(current->getBaseVtkImageData());
245  imageCast->SetOutputScalarTypeToShort();
246  imageCast->Update();
247 
248  appender->AddInputData(imageCast->GetOutput());
249  }
250  appender->Update();
251 
252  vtkImageDataPtr wholeImage = appender->GetOutput();
253  Eigen::Array3d spacing(wholeImage->GetSpacing());
254  spacing[2] = this->getMeanSliceDistance(sorted);
255  wholeImage->SetSpacing(spacing.data());
256 
257  retval->setVtkImageData(wholeImage);
258 
259  return retval;
260 }
261 
263 {
264  QStringList files = mDatabase->filesForSeries(series);
265 
266  std::vector<ImagePtr> images = this->createImages(files);
267 
268  if (images.empty())
269  return ImagePtr();
270 
271  if (images.size()==1)
272  {
273  return images.front();
274  }
275 
276  Vector3D e_sort = images.front()->get_rMd().vector(Vector3D(0,0,1));
277 
278  std::map<double, ImagePtr> sorted = this->sortImagesAlongDirection(images, e_sort);
279 
280  if (!this->slicesFormRegularGrid(sorted, e_sort))
281  return ImagePtr();
282 
283  ImagePtr retval = this->mergeSlices(sorted);
284  return retval;
285 }
286 
287 } /* 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.
vtkSmartPointer< vtkImageAppend > vtkImageAppendPtr
IMAGE_MODALITY convertToModality(QString modalityString)
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
istEMPTY
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
Namespace for all CustusX production code.