fw4spl
io/fwGdcmIO/src/fwGdcmIO/reader/ie/Image.cpp
1 /* ***** BEGIN LICENSE BLOCK *****
2  * FW4SPL - Copyright (C) IRCAD, 2009-2018.
3  * Distributed under the terms of the GNU Lesser General Public License (LGPL) as
4  * published by the Free Software Foundation.
5  * ****** END LICENSE BLOCK ****** */
6 
7 #include "fwGdcmIO/reader/ie/Image.hpp"
8 
9 #include "fwGdcmIO/exception/Failed.hpp"
10 #include "fwGdcmIO/helper/DicomDataReader.hxx"
11 #include "fwGdcmIO/helper/DicomDataTools.hpp"
12 
13 #include <fwData/Image.hpp>
14 
15 #include <fwDataTools/helper/Array.hpp>
16 
17 #include <fwDicomTools/Image.hpp>
18 
19 #include <fwMath/VectorFunctions.hpp>
20 
21 #include <fwMedData/DicomSeries.hpp>
22 
23 #include <boost/algorithm/string/split.hpp>
24 #include <boost/algorithm/string/trim.hpp>
25 #include <boost/assign.hpp>
26 #include <boost/numeric/ublas/io.hpp>
27 #include <boost/numeric/ublas/lu.hpp>
28 
29 #include <gdcmImageApplyLookupTable.h>
30 #include <gdcmImageHelper.h>
31 #include <gdcmImageReader.h>
32 #include <gdcmIPPSorter.h>
33 #include <gdcmPixelFormat.h>
34 #include <gdcmRescaler.h>
35 #include <gdcmUIDGenerator.h>
36 
37 namespace fwGdcmIO
38 {
39 namespace reader
40 {
41 namespace ie
42 {
43 
44 //------------------------------------------------------------------------------
45 
46 Image::Image(const ::fwMedData::DicomSeries::csptr& dicomSeries,
47  const SPTR(::gdcm::Reader)& reader,
48  const ::fwGdcmIO::container::DicomInstance::sptr& instance,
49  const ::fwData::Image::sptr& image,
50  const ::fwLog::Logger::sptr& logger,
51  ProgressCallback progress,
52  CancelRequestedCallback cancel) :
53  ::fwGdcmIO::reader::ie::InformationEntity< ::fwData::Image >(dicomSeries, reader, instance, image,
54  logger, progress, cancel),
55  m_enableBufferRotation(true)
56 {
57 }
58 
59 //------------------------------------------------------------------------------
60 
62 {
63 }
64 
65 //------------------------------------------------------------------------------
66 
67 double getInstanceZPosition(const ::fwMemory::BufferObject::sptr& bufferObj)
68 {
69  ::gdcm::ImageReader reader;
70  const ::fwMemory::BufferManager::StreamInfo streamInfo = bufferObj->getStreamInfo();
71  SPTR(std::istream) is = streamInfo.stream;
72  reader.SetStream(*is);
73 
74  if (!reader.Read())
75  {
76  return 0;
77  }
78 
79  // Retrieve dataset
80  const ::gdcm::DataSet& dataset = reader.GetFile().GetDataSet();
81 
82  // Check tags availability
83  if(!dataset.FindDataElement(::gdcm::Tag(0x0020, 0x0032)) || !dataset.FindDataElement(::gdcm::Tag(0x0020, 0x0037)))
84  {
85  const std::string msg = "Unable to compute the spacing between slices of the series.";
86  throw ::fwGdcmIO::exception::Failed(msg);
87  }
88 
89  // Retrieve image position
90  const ::gdcm::Image& gdcmImage = reader.GetImage();
91  const double* gdcmOrigin = gdcmImage.GetOrigin();
92  const fwVec3d imagePosition = {{ gdcmOrigin[0], gdcmOrigin[1], gdcmOrigin[2] }};
93 
94  // Retrieve image orientation
95  const double* directionCosines = gdcmImage.GetDirectionCosines();
96  const fwVec3d imageOrientationU = {{
97  std::round(directionCosines[0]),
98  std::round(directionCosines[1]),
99  std::round(directionCosines[2])
100  }};
101  const fwVec3d imageOrientationV = {{
102  std::round(directionCosines[3]),
103  std::round(directionCosines[4]),
104  std::round(directionCosines[5])
105  }};
106 
107  //Compute Z direction (cross product)
108  const fwVec3d zVector = ::fwMath::cross(imageOrientationU, imageOrientationV);
109 
110  //Compute dot product to get the index
111  const double index = ::fwMath::dot(imagePosition, zVector);
112 
113  return index;
114 }
115 
116 //------------------------------------------------------------------------------
117 
118 void Image::readImagePlaneModule()
119 {
120  // Retrieve GDCM image
121  SPTR(::gdcm::ImageReader) imageReader = std::static_pointer_cast< ::gdcm::ImageReader >(m_reader);
122  const ::gdcm::Image& gdcmImage = imageReader->GetImage();
123 
124  // Image Position (Patient) - Type 1
125  const double* gdcmOrigin = gdcmImage.GetOrigin();
126  ::fwData::Image::OriginType origin(3, 0);
127  if ( gdcmOrigin != 0 )
128  {
129  std::copy( gdcmOrigin, gdcmOrigin+3, origin.begin() );
130  }
131  m_object->setOrigin(origin);
132 
133  // Pixel Spacing - Type 1
134  // Image dimension
135  const unsigned int dimension = gdcmImage.GetNumberOfDimensions();
136  OSLM_TRACE("Image's dimension : " << dimension);
137 
138  // Image's spacing
139  const double* gdcmSpacing = gdcmImage.GetSpacing();
140  ::fwData::Image::SpacingType spacing(3, 1);
141  if ( gdcmSpacing != 0 )
142  {
143  std::copy( gdcmSpacing, gdcmSpacing+dimension, spacing.begin() );
144  }
145 
146  // Compute Z image spacing
147  const ::fwMedData::DicomSeries::DicomContainerType dicomContainer = m_dicomSeries->getDicomContainer();
148  if(dicomContainer.size() > 1)
149  {
150  auto firstItem = dicomContainer.begin();
151  const auto& lastItem = dicomContainer.rbegin();
152 
153  // Compute the spacing between slices of the 2 first slices.
154  const double firstIndex = getInstanceZPosition(firstItem->second);
155  const double secondIndex = getInstanceZPosition((++firstItem)->second);
156  const double lastIndex = getInstanceZPosition(lastItem->second);
157  spacing[2] = std::abs(secondIndex - firstIndex);
158 
159  // Check that the same spacing is used for all the instances
160  const double epsilon = 1e-2;
161  const double totalZSpacing = std::abs(lastIndex - firstIndex);
162  const double errorGap = std::abs( spacing[2] * static_cast<double>(dicomContainer.size() - 1 ) ) -
163  totalZSpacing;
164  if(errorGap > epsilon)
165  {
166  std::stringstream ss;
167  ss << "Computed spacing between slices may not be correct. (Error gap : " << errorGap << ")";
168  m_logger->warning(ss.str());
169  }
170  }
171  else
172  {
173  // Retrieve dataset
174  const ::gdcm::DataSet& dataset = imageReader->GetFile().GetDataSet();
175  // Check tags availability
176  if(dataset.FindDataElement(::gdcm::Tag(0x0018, 0x0050)))
177  {
178  const std::string& sliceThickness =
179  ::fwGdcmIO::helper::DicomDataReader::getTagValue< 0x0018, 0x0050 >(dataset);
180  spacing[2] = std::stod(sliceThickness);
181  }
182  }
183 
184  OSLM_TRACE("Image's spacing : "<<spacing[0]<<"x"<<spacing[1]<<"x"<<spacing[2]);
185  m_object->setSpacing( spacing );
186 }
187 
188 //------------------------------------------------------------------------------
189 
190 void Image::readVOILUTModule()
191 {
192  // Retrieve dataset
193  const ::gdcm::DataSet& dataset = m_reader->GetFile().GetDataSet();
194 
195  //Image's window center (double)
196  std::string windowCenter = ::fwGdcmIO::helper::DicomDataReader::getTagValue<0x0028, 0x1050>(dataset);
197  std::vector<std::string> splitedWindowCenters;
198  if ( !windowCenter.empty() )
199  {
200  // If there is several window center we only take the first one
201  ::boost::split( splitedWindowCenters, windowCenter, ::boost::is_any_of( "\\" ) );
202  m_object->setWindowCenter( ::boost::lexical_cast< double >(splitedWindowCenters[0]));
203  }
204  OSLM_TRACE("Image's window center : " << m_object->getWindowCenter());
205 
206  //Image's window width (double)
207  std::string windowWidth = ::fwGdcmIO::helper::DicomDataReader::getTagValue<0x0028, 0x1051>(dataset);
208  std::vector<std::string> splitedWindowWidth;
209  if ( !windowWidth.empty() )
210  {
211  // If there is several window width we only take the first one
212  ::boost::split( splitedWindowWidth, windowWidth, ::boost::is_any_of( "\\" ) );
213  m_object->setWindowWidth( ::boost::lexical_cast< double >(splitedWindowWidth[0]));
214  }
215  OSLM_TRACE("Image's window width : " << m_object->getWindowWidth());
216 
217 }
218 
219 //------------------------------------------------------------------------------
220 
221 std::vector<double> getRescaleInterceptSlopeValue(::gdcm::ImageReader* imageReader)
222 {
223  // Retrieve dataset
224  const ::gdcm::DataSet& dataset = imageReader->GetFile().GetDataSet();
225 
226  // Retrieve rescale values
227  std::vector< double > rescale = ::gdcm::ImageHelper::GetRescaleInterceptSlopeValue(imageReader->GetFile());
228 
229  // Correct Rescale Intercept and Rescale Slope as GDCM may fail to retrieve them.
230  if(dataset.FindDataElement(::gdcm::Tag(0x0028, 0x1052)) && dataset.FindDataElement(::gdcm::Tag(0x0028, 0x1053)))
231  {
232  rescale[0] = ::fwGdcmIO::helper::DicomDataReader::getTagValue<0x0028, 0x1052, double>(dataset);
233  rescale[1] = ::fwGdcmIO::helper::DicomDataReader::getTagValue<0x0028, 0x1053, double>(dataset);
234  }
235 
236  return rescale;
237 }
238 
239 //------------------------------------------------------------------------------
240 
241 void Image::readImagePixelModule()
242 {
243  // Retrieve GDCM image
244  SPTR(::gdcm::ImageReader) imageReader = std::static_pointer_cast< ::gdcm::ImageReader >(m_reader);
245  const ::gdcm::Image& gdcmImage = imageReader->GetImage();
246 
247  // Retrieve dataset
248  const ::gdcm::DataSet& dataset = m_reader->GetFile().GetDataSet();
249 
250  // Retrieve image information before processing the rescaling
251  ::gdcm::PixelFormat pixelFormat = ::gdcm::ImageHelper::GetPixelFormatValue(imageReader->GetFile());
252 
253  // Retrieve rescale intercept/slope values
254  std::vector<double> rescale = getRescaleInterceptSlopeValue(imageReader.get());
255  double rescaleIntercept = rescale[0];
256  double rescaleSlope = rescale[1];
257 
258  const unsigned short samplesPerPixel = pixelFormat.GetSamplesPerPixel();
259  const unsigned short bitsAllocated = pixelFormat.GetBitsAllocated();
260  const unsigned short bitsStored = pixelFormat.GetBitsStored();
261  const unsigned short highBit = pixelFormat.GetHighBit();
262  const unsigned short pixelRepresentation = pixelFormat.GetPixelRepresentation();
263 
264  // Compute final image type
265  ::fwDicomTools::Image imageHelper(
266  samplesPerPixel, bitsAllocated, bitsStored, highBit, pixelRepresentation, rescaleSlope, rescaleIntercept);
267  ::fwTools::Type imageType = imageHelper.findImageTypeFromMinMaxValues();
268  m_object->setType(imageType);
269  ::gdcm::PixelFormat targetPixelFormat = ::fwGdcmIO::helper::DicomDataTools::getPixelType(m_object);
270 
271  if(targetPixelFormat == ::gdcm::PixelFormat::UNKNOWN)
272  {
273  throw ::fwGdcmIO::exception::Failed("Unsupported image pixel format.");
274  }
275 
276  // Compute number of components
277  const std::string photometricInterpretation =
278  ::fwGdcmIO::helper::DicomDataReader::getTagValue<0x0028, 0x0004>(dataset);
279  const std::string pixelPresentation =
280  ::fwGdcmIO::helper::DicomDataReader::getTagValue<0x0008, 0x9205>(dataset);
281 
282  if(photometricInterpretation == "MONOCHROME2")
283  {
284  m_object->setNumberOfComponents(1);
285  }
286  else if(photometricInterpretation == "RGB" || photometricInterpretation == "YBR")
287  {
288  m_object->setNumberOfComponents(3);
289  }
290  else if(photometricInterpretation == "ARGB" || photometricInterpretation == "CMYK")
291  {
292  m_object->setNumberOfComponents(4);
293  }
294  else if(photometricInterpretation == "PALETTE COLOR" || pixelPresentation == "COLOR")
295  {
296  m_object->setNumberOfComponents(3);
297  }
298  else
299  {
300  const std::string msg = "The photometric interpretation \"" + photometricInterpretation
301  + "\" is not supported.";
302  throw ::fwGdcmIO::exception::Failed(msg);
303  }
304 
305  // Retrieve image dimensions
306  std::vector<unsigned int> dimensions = ::gdcm::ImageHelper::GetDimensionsValue(imageReader->GetFile());
307 
308  // Compute real image size (we assume every instance has the same number of
309  // slices (1 for CT and MR, may be more for enhanced CT and MR)
310  const unsigned long frameBufferSize = gdcmImage.GetBufferLength();
311  const unsigned long depth = frameBufferSize / (dimensions[0] * dimensions[1] * (bitsAllocated/8));
312  dimensions[2] = static_cast<unsigned int>(m_dicomSeries->getDicomContainer().size() * depth);
313  m_object->setSize(::boost::assign::list_of(dimensions[0])(dimensions[1])(dimensions[2]));
314 
315  OSLM_TRACE("Image dimensions : [" << dimensions[0] << "," <<dimensions[1] << "," << dimensions[2] << "]");
316 
317  const unsigned long imageBufferSize =
318  dimensions[0] * dimensions[1] * dimensions[2] * (bitsAllocated/8);
319  const unsigned long newImageBufferSize =
320  dimensions[0] * dimensions[1] * dimensions[2] * (targetPixelFormat.GetBitsAllocated()/8);
321 
322  OSLM_TRACE("Image buffer size : " << imageBufferSize);
323  OSLM_TRACE("New image buffer size : " << newImageBufferSize);
324 
325  // Let's read the image buffer
326  bool performRescale = (photometricInterpretation != "PALETTE COLOR" && pixelPresentation != "COLOR");
327  char* imageBuffer = this->readImageBuffer(dimensions, bitsAllocated, targetPixelFormat.GetBitsAllocated(),
328  performRescale);
329 
330  // Correct image buffer according to the image orientation
331  if(!(m_cancelRequestedCallback && m_cancelRequestedCallback()) && m_enableBufferRotation)
332  {
333  imageBuffer = this->correctImageOrientation(imageBuffer, dimensions,
334  (performRescale) ? targetPixelFormat.GetBitsAllocated() : bitsAllocated);
335  }
336 
337  // Apply lookup table if required
338  if(!(m_cancelRequestedCallback && m_cancelRequestedCallback()) &&
339  (photometricInterpretation == "PALETTE COLOR" || pixelPresentation == "COLOR"))
340  {
341  try
342  {
343  // Create new buffer
344  char* coloredBuffer = 0;
345  coloredBuffer = new char[newImageBufferSize*3];
346 
347  // Apply lookup
348  gdcmImage.GetLUT().Decode(coloredBuffer, newImageBufferSize*3, imageBuffer, imageBufferSize);
349 
350  // Swap buffers
351  delete[] imageBuffer;
352  imageBuffer = coloredBuffer;
353  }
354  catch (...)
355  {
356  throw ::fwGdcmIO::exception::Failed("There is not enough memory available to open this image.");
357  }
358  }
359 
360  // Set image buffer
361  ::fwData::Array::sptr array = m_object->getDataArray();
362  ::fwDataTools::helper::Array helper(array);
363  helper.setBuffer(imageBuffer, true, m_object->getType(), m_object->getSize(), m_object->getNumberOfComponents());
364 
365 }
366 
367 //------------------------------------------------------------------------------
368 
369 char* Image::readImageBuffer(const std::vector<unsigned int>& dimensions,
370  const unsigned short bitsAllocated,
371  const unsigned short newBitsAllocated,
372  const bool performRescale) throw(::fwGdcmIO::exception::Failed)
373 {
374  // Retrieve GDCM image
375  SPTR(::gdcm::ImageReader) imageReader = std::static_pointer_cast< ::gdcm::ImageReader >(m_reader);
376  const ::gdcm::Image& gdcmFirstImage = imageReader->GetImage();
377 
378  // Path container
379  ::fwMedData::DicomSeries::DicomContainerType dicomContainer = m_dicomSeries->getDicomContainer();
380 
381  // Raw buffer for all frames
382  char* frameBuffer;
383  char* imageBuffer;
384  const unsigned long frameBufferSize = gdcmFirstImage.GetBufferLength();
385  const unsigned long newFrameBufferSize = frameBufferSize * (newBitsAllocated/bitsAllocated);
386  const unsigned long imageBufferSize = dimensions.at(0) * dimensions.at(1) * dimensions.at(2) *
387  ((performRescale ? newBitsAllocated : bitsAllocated)/8);
388 
389  // Allocate raw buffer
390  try
391  {
392  frameBuffer = new char[frameBufferSize];
393  imageBuffer = new char[imageBufferSize];
394  }
395  catch (...)
396  {
397  throw ::fwGdcmIO::exception::Failed("There is not enough memory available to open this image.");
398  }
399 
400  // Read every frames
401  unsigned int frameNumber = 0;
402  for(const auto& item : dicomContainer)
403  {
404  // Read a frame
405  ::gdcm::ImageReader frameReader;
406  const ::fwMemory::BufferObject::sptr bufferObj = item.second;
407  const ::fwMemory::BufferManager::StreamInfo streamInfo = bufferObj->getStreamInfo();
408  const std::string dicomPath = bufferObj->getStreamInfo().fsFile.string();
409  SPTR(std::istream) is = streamInfo.stream;
410  frameReader.SetStream(*is);
411 
412  if ( frameReader.Read() )
413  {
414  const ::gdcm::Image& gdcmImage = frameReader.GetImage();
415  // Check frame buffer size
416  if(frameBufferSize != gdcmImage.GetBufferLength())
417  {
418  throw ::fwGdcmIO::exception::Failed("The frame buffer does not have the expected size : " + dicomPath);
419  }
420 
421  // Get raw buffer and set it in the image buffer
422  if ( !gdcmImage.GetBuffer( frameBuffer ) )
423  {
424  throw ::fwGdcmIO::exception::Failed("Failed to get a frame buffer");
425  }
426  }
427  else
428  {
429  std::stringstream ss;
430  ss << "Reading error on frame : " << frameNumber;
431  throw ::fwGdcmIO::exception::Failed(ss.str());
432  }
433 
434  // Rescale
435  if(performRescale)
436  {
437  // Retrieve rescale intercept/slope values
438  std::vector<double> rescale = getRescaleInterceptSlopeValue(&frameReader);
439  double rescaleIntercept = rescale[0];
440  double rescaleSlope = rescale[1];
441 
442  // Retrieve image information before processing the rescaling
443  ::gdcm::PixelFormat pixelFormat =
444  ::gdcm::ImageHelper::GetPixelFormatValue(frameReader.GetFile());
445  ::gdcm::PixelFormat::ScalarType scalarType = pixelFormat.GetScalarType();
446  ::gdcm::PixelFormat targetPixelFormat = ::fwGdcmIO::helper::DicomDataTools::getPixelType(m_object);
447 
448  if(targetPixelFormat == ::gdcm::PixelFormat::UNKNOWN)
449  {
450  throw ::fwGdcmIO::exception::Failed("Unsupported image pixel format.");
451  }
452 
453  // Create rescaler
454  ::gdcm::Rescaler rescaler;
455  rescaler.SetIntercept(rescaleIntercept);
456  rescaler.SetSlope(rescaleSlope);
457  rescaler.SetPixelFormat(scalarType);
458  rescaler.SetTargetPixelType(targetPixelFormat.GetScalarType());
459  rescaler.SetUseTargetPixelType(true);
460 
461  // Rescale the image
462  rescaler.Rescale(imageBuffer + (frameNumber * newFrameBufferSize), frameBuffer, frameBufferSize);
463  }
464  else
465  {
466  // Copy bytes
467  memcpy(imageBuffer + frameNumber * frameBufferSize, frameBuffer, frameBufferSize);
468  }
469 
470  // Reference SOP Instance UID in dicomInstance for SR reading
471  const ::gdcm::DataSet& gdcmDatasetRoot = frameReader.GetFile().GetDataSet();
472  const std::string sopInstanceUID = ::fwGdcmIO::helper::DicomDataReader::getTagValue<0x0008, 0x0018>(
473  gdcmDatasetRoot);
474  if(!sopInstanceUID.empty())
475  {
476  m_instance->getSOPInstanceUIDContainer().push_back(sopInstanceUID);
477  }
478  else
479  {
480  m_logger->warning("A frame with an undefined SOP instance UID has been detected.");
481  }
482 
483  // Next frame
484  ++frameNumber;
485 
486  unsigned int progress =
487  static_cast<unsigned int>(18 + (frameNumber*100/static_cast<double>(dicomContainer.size())) * 0.6);
488  m_progressCallback(progress);
489 
490  if(m_cancelRequestedCallback && m_cancelRequestedCallback())
491  {
492  break;
493  }
494  }
495 
496  // Delete frame buffer
497  delete[] frameBuffer;
498 
499  return imageBuffer;
500 }
501 
502 //------------------------------------------------------------------------------
503 
504 char* Image::correctImageOrientation(char* buffer,
505  const std::vector<unsigned int>& dimensions,
506  unsigned short bitsAllocated)
507 {
508  char* result = buffer;
509 
510  // Retrieve GDCM image
511  SPTR(::gdcm::ImageReader) imageReader = std::static_pointer_cast< ::gdcm::ImageReader >(m_reader);
512  const ::gdcm::Image& gdcmImage = imageReader->GetImage();
513 
514  // Retrieve image orientation
515  const double* directionCosines = gdcmImage.GetDirectionCosines();
516 
517  // Compute U vector
518  fwVec3d imageOrientationU = {{
519  std::round(directionCosines[0]),
520  std::round(directionCosines[1]),
521  std::round(directionCosines[2])
522  }};
523  // Try to find the closest axe
524  if((std::fabs(imageOrientationU[0]) + std::fabs(imageOrientationU[1]) + std::fabs(imageOrientationU[2])) > 1)
525  {
526  if(std::fabs(directionCosines[0]) < std::fabs(directionCosines[1]) ||
527  std::fabs(directionCosines[0]) < std::fabs(directionCosines[2]))
528  {
529  imageOrientationU[0] = 0;
530  }
531  if(std::fabs(directionCosines[1]) < std::fabs(directionCosines[0]) ||
532  std::fabs(directionCosines[1]) < std::fabs(directionCosines[2]))
533  {
534  imageOrientationU[1] = 0;
535  }
536  if(std::fabs(directionCosines[2]) < std::fabs(directionCosines[0]) ||
537  std::fabs(directionCosines[2]) < std::fabs(directionCosines[1]))
538  {
539  imageOrientationU[2] = 0;
540  }
541  m_logger->warning("Unable to determine clearly the orientation of the image. "
542  "The software may display the image in the wrong direction.");
543  }
544 
545  // Compute V vector
546  fwVec3d imageOrientationV = {{
547  std::round(directionCosines[3]),
548  std::round(directionCosines[4]),
549  std::round(directionCosines[5])
550  }};
551  // Try to find the closest axe
552  if((std::fabs(imageOrientationV[0]) + std::fabs(imageOrientationV[1]) + std::fabs(imageOrientationV[2])) > 1)
553  {
554  if(std::fabs(directionCosines[3]) < std::fabs(directionCosines[4]) ||
555  std::fabs(directionCosines[3]) < std::fabs(directionCosines[5]))
556  {
557  imageOrientationV[0] = 0;
558  }
559  if(std::fabs(directionCosines[4]) < std::fabs(directionCosines[3]) ||
560  std::fabs(directionCosines[4]) < std::fabs(directionCosines[5]))
561  {
562  imageOrientationV[1] = 0;
563  }
564  if(std::fabs(directionCosines[5]) < std::fabs(directionCosines[3]) ||
565  std::fabs(directionCosines[5]) < std::fabs(directionCosines[4]))
566  {
567  imageOrientationV[2] = 0;
568  }
569  m_logger->warning("Unable to determine clearly the orientation of the image. "
570  "The software may display the image in the wrong direction.");
571  }
572 
573  // Compute W vector
574  const fwVec3d imageOrientationW = ::fwMath::cross(imageOrientationU, imageOrientationV);
575 
576  // Create orientation matrix
577  Image::MatrixType matrix(4, 4);
578  matrix(0, 0) = imageOrientationU[0];
579  matrix(1, 0) = imageOrientationU[1];
580  matrix(2, 0) = imageOrientationU[2];
581  matrix(3, 0) = 0;
582  matrix(0, 1) = imageOrientationV[0];
583  matrix(1, 1) = imageOrientationV[1];
584  matrix(2, 1) = imageOrientationV[2];
585  matrix(3, 1) = 0;
586  matrix(0, 2) = imageOrientationW[0];
587  matrix(1, 2) = imageOrientationW[1];
588  matrix(2, 2) = imageOrientationW[2];
589  matrix(3, 2) = 0;
590  matrix(0, 3) = 0;
591  matrix(1, 3) = 0;
592  matrix(2, 3) = 0;
593  matrix(3, 3) = 1;
594 
595  // Compute inverse matrix in order to rotate the buffer
596  Image::MatrixType inverseMatrix = this->computeInverseMatrix(matrix);
597  Image::MatrixType identityMatrix = ::boost::numeric::ublas::identity_matrix< double >(inverseMatrix.size1());
598 
599  // Check whether the image must be rotated or not
600  if(!::boost::numeric::ublas::detail::expression_type_check(inverseMatrix, identityMatrix))
601  {
602  // Compute new image size
603  VectorType sizeVector(4);
604  sizeVector(0) = dimensions.at(0);
605  sizeVector(1) = dimensions.at(1);
606  sizeVector(2) = dimensions.at(2);
607  VectorType newSizeVector = ::boost::numeric::ublas::prod(sizeVector, inverseMatrix);
608  const unsigned short newSizeX = static_cast<unsigned short>(std::fabs(newSizeVector[0]));
609  const unsigned short newSizeY = static_cast<unsigned short>(std::fabs(newSizeVector[1]));
610  const unsigned short newSizeZ = static_cast<unsigned short>(std::fabs(newSizeVector[2]));
611  newSizeVector(0) = newSizeX;
612  newSizeVector(1) = newSizeY;
613  newSizeVector(2) = newSizeZ;
614 
615  // Compute old size from the new absolute size in order to retrieve pixel flips
616  VectorType oldSizeVector = ::boost::numeric::ublas::prod(newSizeVector, matrix);
617 
618  // Create new buffer to store rotated image
619  const unsigned long size = dimensions.at(0) * dimensions.at(1) * dimensions.at(2) * (bitsAllocated/8);
620  char* newBuffer = new char[size];
621 
622  // Rotate image
623  unsigned short x, y, z, oldx, oldy, oldz;
624  for(z = 0; z < newSizeZ && !(m_cancelRequestedCallback && m_cancelRequestedCallback()); ++z)
625  {
626  for(y = 0; y < newSizeY; ++y)
627  {
628  for(x = 0; x < newSizeX; ++x)
629  {
630  // Create new position
631  VectorType newPosition(4);
632  newPosition(0) = x;
633  newPosition(1) = y;
634  newPosition(2) = z;
635 
636  // Compute old position
637  VectorType oldPosition = ::boost::numeric::ublas::prod(newPosition, matrix);
638  oldx = (oldSizeVector[0] > 0) ? static_cast<unsigned short>(oldPosition[0])
639  : static_cast<unsigned short>((dimensions.at(0)-1) + oldPosition[0]);
640  oldy = (oldSizeVector[1] > 0) ? static_cast<unsigned short>(oldPosition[1])
641  : static_cast<unsigned short>((dimensions.at(1)-1)+ oldPosition[1]);
642  oldz = (oldSizeVector[2] > 0) ? static_cast<unsigned short>(oldPosition[2])
643  : static_cast<unsigned short>((dimensions.at(2)-1)+ oldPosition[2]);
644 
645  // Compute indices
646  unsigned int positionIndex = (x + (y*newSizeX) + z*(newSizeX*newSizeY)) * (bitsAllocated/8);
647  unsigned int oldPositionIndex =
648  (oldx + (oldy*dimensions.at(0)) + oldz*(dimensions.at(0)*dimensions.at(1))) *
649  (bitsAllocated/8);
650 
651  // Copy bytes
652  memcpy(&newBuffer[positionIndex], &buffer[oldPositionIndex], (bitsAllocated/8));
653  }
654  }
655  unsigned int progress = static_cast<unsigned int>(78 + (z*100./newSizeZ) * 0.2);
656  m_progressCallback(progress);
657  }
658 
659  result = newBuffer;
660  delete[] buffer;
661 
662  // Update image size
663  m_object->setSize(::boost::assign::list_of(newSizeX)(newSizeY)(newSizeZ));
664 
665  // Update image spacing
666  ::fwData::Image::SpacingType spacing = m_object->getSpacing();
667  VectorType spacingVector(4);
668  spacingVector(0) = spacing[0];
669  spacingVector(1) = spacing[1];
670  spacingVector(2) = spacing[2];
671  VectorType newSpacingVector = ::boost::numeric::ublas::prod(spacingVector, inverseMatrix);
672  ::fwData::Image::SpacingType newSpacing(3, 1);
673  newSpacing[0] = std::fabs(newSpacingVector[0]);
674  newSpacing[1] = std::fabs(newSpacingVector[1]);
675  newSpacing[2] = std::fabs(newSpacingVector[2]);
676  m_object->setSpacing( newSpacing );
677 
678  // Update image origin
679  ::fwData::Image::OriginType origin = m_object->getOrigin();
680  VectorType originVector(4);
681  originVector(0) = origin[0];
682  originVector(1) = origin[1];
683  originVector(2) = origin[2];
684  VectorType newOriginVector = ::boost::numeric::ublas::prod(originVector, inverseMatrix);
685  ::fwData::Image::OriginType newOrigin(3, 0);
686  newOrigin[0] = newOriginVector[0];
687  newOrigin[1] = newOriginVector[1];
688  newOrigin[2] = newOriginVector[2];
689  m_object->setOrigin( newOrigin );
690 
691  m_logger->warning("Image buffer has been rotated in order to match patient orientation: "
692  "image origin could be wrong.");
693  }
694 
695  return result;
696 }
697 
698 //------------------------------------------------------------------------------
699 
700 Image::MatrixType Image::computeInverseMatrix(MatrixType matrix)
701 {
702  // Create output matrix (identity)
703  Image::MatrixType output(matrix.size1(), matrix.size2());
704  output.assign(::boost::numeric::ublas::identity_matrix< double >(output.size1()));
705 
706  // Create a permutation matrix for the LU-factorization
707  boost::numeric::ublas::permutation_matrix< std::size_t > perm(matrix.size1());
708 
709  // Perform LU-factorization
710  long unsigned int res = boost::numeric::ublas::lu_factorize(matrix, perm);
711  if (res != 0)
712  {
713  SLM_WARN("Cannot compute matrix.");
714  }
715  else
716  {
717  // Backsubstitute to get the inverse
718  boost::numeric::ublas::lu_substitute(matrix, perm, output);
719  }
720 
721  // Return inverse matrix or identity
722  return output;
723 }
724 
725 //------------------------------------------------------------------------------
726 
727 } // namespace ie
728 } // namespace reader
729 } // namespace fwGdcmIO
#define SPTR(_cls_)
FWDICOMTOOLS_API::fwTools::Type findImageTypeFromMinMaxValues() const
Find Image Type.
virtual FWDICOMTOOLS_API ~Image()
Destructor.
#define OSLM_TRACE(message)
Definition: spyLog.hpp:230
The namespace fwGdcmIO contains reader, writer and helper for dicom data.
#define SLM_WARN(message)
Definition: spyLog.hpp:261
FWDATATOOLS_API void setBuffer(void *buf, bool takeOwnership, const ::fwTools::Type &type, const ::fwData::Array::SizeType &size, size_t nbOfComponents,::fwMemory::BufferAllocationPolicy::sptr policy=::fwMemory::BufferMallocPolicy::New())
Setter for the array buffer.
Implements a failed exception class for fwGdcmIO.
Class describing an elementary C++ type aka unsigned char, signed char, .... int, float...
Definition: Type.hpp:32
std::vector< double > SpacingType
Image spacing type.
Contains the representation of the data objects used in the framework.
static FWGDCMIO_APIconst::gdcm::PixelFormat getPixelType(const ::fwData::Image::csptr &image)
Return the pixel type of a fwData Image.
Helper to manage array buffer. Lock the buffer before to modify it.
FWGDCMIO_API Image(const std::shared_ptr< const ::fwMedData::DicomSeries > &dicomSeries, const std::shared_ptr< ::gdcm::Reader > &reader, const std::shared_ptr< ::fwGdcmIO::container::DicomInstance > &instance, const ::fwData::Image::sptr &image, const ::fwLog::Logger::sptr &logger=nullptr, ProgressCallback progress=nullptr, CancelRequestedCallback cancel=nullptr)
Constructor.
std::vector< double > OriginType
Image origin type.