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 
74 QString DicomConverter::generateName(DicomImageReaderPtr reader)
75 {
76  QString seriesDescription = reader->item()->GetElementAsString(DCM_SeriesDescription);
77  QString name = convertToValidName(seriesDescription);
78  return name;
79 }
80 
81 ImagePtr DicomConverter::createCxImageFromDicomFile(QString filename, bool ignoreLocalizerImages)
82 {
84  if (!reader)
85  {
86  reportWarning(QString("File not found: %1").arg(filename));
87  return ImagePtr();
88  }
89 
90  if(ignoreLocalizerImages && reader->isLocalizerImage())
91  {
92  reportWarning(QString("Localizer image removed from series: %1").arg(filename));
93  return ImagePtr();
94  }
95 
96  if (reader->getNumberOfFrames()==0)
97  {
98  reportWarning(QString("Found no images in %1, skipping.").arg(filename));
99  return ImagePtr();
100  }
101 
102  QString uid = this->generateUid(reader);
103  QString name = this->generateName(reader);
104  cx::ImagePtr image = cx::Image::create(uid, name);
105 
106  vtkImageDataPtr imageData = reader->createVtkImageData();
107  if (!imageData)
108  {
109  reportWarning(QString("Failed to create image for %1.").arg(filename));
110  return ImagePtr();
111  }
112  image->setVtkImageData(imageData);
113 
114  QString modalityString = reader->item()->GetElementAsString(DCM_Modality);
115  image->setModality(convertToModality(modalityString));
116 
117  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.
118 
119  DicomImageReader::WindowLevel windowLevel = reader->getWindowLevel();
120  image->setInitialWindowLevel(windowLevel.width, windowLevel.center);
121 
122  Transform3D M = reader->getImageTransformPatient();
123  image->get_rMd_History()->setRegistration(M);
124 
125 // reportDebug(QString("Image created from %1").arg(filename));
126  return image;
127 }
128 
129 std::vector<ImagePtr> DicomConverter::createImages(QStringList files)
130 {
131  std::vector<ImagePtr> retval;
132  for (int i=0; i<files.size(); ++i)
133  {
134  bool ignoreSpesialImages = true;
135  ImagePtr image = this->createCxImageFromDicomFile(files[i], ignoreSpesialImages);
136  if (image)
137  retval.push_back(image);
138  }
139  return retval;
140 }
141 
142 std::map<double, ImagePtr> DicomConverter::sortImagesAlongDirection(std::vector<ImagePtr> images, Vector3D e_sort)
143 {
144  std::map<double, ImagePtr> sorted;
145  for (unsigned i=0; i<images.size(); ++i)
146  {
147  Vector3D pos = images[i]->get_rMd().coord(Vector3D(0,0,0));
148  double dist = dot(pos, e_sort);
149 
150  sorted[dist] = images[i];
151  }
152  return sorted;
153 }
154 
155 bool DicomConverter::slicesFormRegularGrid(std::map<double, ImagePtr> sorted, Vector3D e_sort) const
156 {
157  std::vector<Vector3D> positions;
158  std::vector<double> distances;
159  for (std::map<double, ImagePtr>::iterator iter=sorted.begin(); iter!=sorted.end(); ++iter)
160  {
161  ImagePtr current = iter->second;
162 
163  Vector3D pos = current->get_rMd().coord(Vector3D(0,0,0));
164  positions.push_back(pos);
165 
166  if (positions.size()>=2)
167  {
168  Vector3D p0 = positions[positions.size()-2];
169  Vector3D p1 = positions[positions.size()-1];
170  double dist = dot(p1-p0, e_sort);
171  distances.push_back(dist);
172 
173  Vector3D tilt = cross(p1-p0, e_sort);
174  double sliceGantryTiltTolerance = 0.001;
175  if (!similar(tilt.length(), 0.0, sliceGantryTiltTolerance))
176  {
177  reportError(QString("Dicom convert: found gantry tilt: %1, cannot create image.").arg(tilt.length()));
178  return false;
179  }
180  }
181 
182  if (distances.size()>=2)
183  {
184  double d0 = distances[distances.size()-2];
185  double d1 = distances[distances.size()-1];
186  double sliceSpacingTolerance = 0.01;
187  if (!similar(d0, d1, sliceSpacingTolerance))
188  {
189  reportError(QString("Dicom convert: found uneven slice spacing: %1 != %2, cannot create image.").arg(d0).arg(d1));
190  return false;
191  }
192  }
193  }
194 
195  return true;
196 }
197 
198 double DicomConverter::getMeanSliceDistance(std::map<double, ImagePtr> sorted) const
199 {
200  if (sorted.size()==0)
201  return 0;
202 
203  // check for multislice image
204  vtkImageDataPtr first = sorted.begin()->second->getBaseVtkImageData();
205  if (first->GetDimensions()[2]>1)
206  return first->GetSpacing()[2];
207 
208  if (sorted.size()<2)
209  return 0;
210 
211  // use average of all slices
212  double zValueLastImage = sorted.rbegin()->first;
213  double zValueFirstImage = sorted.begin()->first;
214  unsigned long numHolesBetweenImages = sorted.size() - 1;
215  return (zValueLastImage-zValueFirstImage)/numHolesBetweenImages;
216 }
217 
218 ImagePtr DicomConverter::mergeSlices(std::map<double, ImagePtr> sorted) const
219 {
220  vtkImageAppendPtr appender = vtkImageAppendPtr::New();
221  appender->SetAppendAxis(2);
222 
223  ImagePtr retval = sorted.begin()->second;
224 
225  int i = 0;
226 
227  for (std::map<double, ImagePtr>::iterator iter=sorted.begin(); iter!=sorted.end(); ++iter)
228  {
229  ImagePtr current = iter->second;
230 
231  // Set window width and level to the values of the middle frame
232  if (i == sorted.size() / 2)
233  retval->setInitialWindowLevel(current->getInitialWindowWidth(), current->getInitialWindowLevel());
234  ++i;
235 
236  //Convert all slices to same format
237  vtkImageCastPtr imageCast = vtkImageCastPtr::New();
238  imageCast->SetInputData(current->getBaseVtkImageData());
239  imageCast->SetOutputScalarTypeToShort();
240  imageCast->Update();
241 
242  appender->AddInputData(imageCast->GetOutput());
243  }
244  appender->Update();
245 
246  vtkImageDataPtr wholeImage = appender->GetOutput();
247  Eigen::Array3d spacing(wholeImage->GetSpacing());
248  spacing[2] = this->getMeanSliceDistance(sorted);
249  wholeImage->SetSpacing(spacing.data());
250 
251  retval->setVtkImageData(wholeImage);
252 
253  return retval;
254 }
255 
257 {
258  QStringList files = mDatabase->filesForSeries(series);
259 
260  std::vector<ImagePtr> images = this->createImages(files);
261 
262  if (images.empty())
263  return ImagePtr();
264 
265  if (images.size()==1)
266  {
267  return images.front();
268  }
269 
270  Vector3D e_sort = images.front()->get_rMd().vector(Vector3D(0,0,1));
271 
272  std::map<double, ImagePtr> sorted = this->sortImagesAlongDirection(images, e_sort);
273 
274  if (!this->slicesFormRegularGrid(sorted, e_sort))
275  return ImagePtr();
276 
277  ImagePtr retval = this->mergeSlices(sorted);
278  return retval;
279 }
280 
281 } /* 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.