fw4spl
reader/ie/Surface.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/container/DicomSurface.hpp"
8 #include "fwGdcmIO/reader/ie/Surface.hpp"
9 
10 #include "fwGdcmIO/helper/DicomDataReader.hxx"
11 #include "fwGdcmIO/helper/DicomDataTools.hpp"
12 
13 #include <fwData/Color.hpp>
14 #include <fwData/Reconstruction.hpp>
15 
16 #include <fwDataIO/reader/DictionaryReader.hpp>
17 
18 #include <fwDataTools/helper/Mesh.hpp>
19 
20 #include <fwMedData/DicomSeries.hpp>
21 
22 #include <fwRuntime/profile/Profile.hpp>
23 
24 #include <boost/algorithm/clamp.hpp>
25 #include <boost/algorithm/string/trim.hpp>
26 #include <boost/date_time/posix_time/posix_time.hpp>
27 
28 // Removes RGB macro defined in windows.h
29 // to avoid conflicts in gdcmSurfaceHelper.h
30 #undef RGB
31 #include <gdcmSurfaceHelper.h>
32 
33 #include <sstream>
34 
35 namespace fwGdcmIO
36 {
37 namespace reader
38 {
39 namespace ie
40 {
41 
42 //------------------------------------------------------------------------------
43 
44 Surface::Surface(const ::fwMedData::DicomSeries::csptr& dicomSeries,
45  const SPTR(::gdcm::Reader)& reader,
46  const ::fwGdcmIO::container::DicomInstance::sptr& instance,
47  const ::fwMedData::ModelSeries::sptr& series,
48  const ::fwLog::Logger::sptr& logger,
49  ProgressCallback progress,
50  CancelRequestedCallback cancel) :
51  ::fwGdcmIO::reader::ie::InformationEntity< ::fwMedData::ModelSeries >(dicomSeries, reader, instance, series,
52  logger, progress, cancel)
53 {
54 }
55 
56 //------------------------------------------------------------------------------
57 
58 Surface::~Surface()
59 {
60 }
61 
62 //------------------------------------------------------------------------------
63 
64 bool Surface::loadSegmentedPropertyRegistry(const ::boost::filesystem::path& filepath)
65 {
66  return m_segmentedPropertyRegistry.readSegmentedPropertyRegistryFile(filepath, true, m_logger);
67 }
68 
69 //------------------------------------------------------------------------------
70 
71 void Surface::readSurfaceSegmentationAndSurfaceMeshModules()
72 {
73  // Retrieve Surface Reader
74  SPTR(::gdcm::SurfaceReader) surfaceReader = std::static_pointer_cast< ::gdcm::SurfaceReader >(m_reader);
75 
76  // Retrieve dataset
77  const ::gdcm::DataSet& dataset = surfaceReader->GetFile().GetDataSet();
78 
79  // Segment Sequence (0x0062,0x0002) - Type 1
80  const ::gdcm::DataElement& sequenceDataElement = dataset.GetDataElement(::gdcm::Tag(0x0062, 0x0002));
81  const auto segmentSequence = sequenceDataElement.GetValueAsSQ();
82 
83  // Segment container
84  const auto& segmentContainer = surfaceReader->GetSegments();
85 
86  // Retrieve reconstruction DB
87  ::fwMedData::ModelSeries::ReconstructionVectorType reconstructionDB = m_object->getReconstructionDB();
88 
89  // Lambda used to display reading errors
90  auto displayError = [&](const ::gdcm::SmartPointer< ::gdcm::Segment >& segment,
91  const std::string& error)
92  {
93  std::string segmentLabel = segment->GetSegmentLabel();
94  segmentLabel = ::gdcm::LOComp::Trim(segmentLabel.c_str());
95  const std::string msg = "Error while reading the '" + segmentLabel + "' segment: " + error;
96 
97  SLM_WARN_IF(msg, !m_logger);
98  if(m_logger)
99  {
100  m_logger->critical(msg);
101  }
102  };
103 
104  //===================
105  // Read segmentations
106  //===================
107  std::size_t itemIndex = 1;
108  for(const auto& segment : segmentContainer)
109  {
110  // We only handle segment containing one surface
111  if (segment->GetSurfaceCount() != 1 || segment->GetSurfaces().size() != 1)
112  {
113  displayError(segment, "Unsupported surface count.");
114  continue;
115  }
116 
117  // Create the reconstruction
118  ::fwData::Reconstruction::sptr reconstruction = ::fwData::Reconstruction::New();
119 
120  // Retrieve the Segment Sequence Item
121  const ::gdcm::Item& segmentItem = segmentSequence->GetItem(itemIndex++);
122 
123  // Get the associated surface of the current segmentation
124  const auto& surfaceContainer = segment->GetSurfaces();
125  const ::gdcm::SmartPointer< ::gdcm::Surface >& surface = surfaceContainer[0];
126 
127  try
128  {
129  // Read the Surface Segmentation Module - PS 3.3 C.8.23.1
130  readSurfaceSegmentationModule(reconstruction, segment, segmentItem);
131 
132  // Read the Surface Mesh Module - PS 3.3 C.27.1
133  readSurfaceMeshModule(reconstruction, surface);
134  }
135  catch(const ::fwGdcmIO::exception::Failed& exception)
136  {
137  displayError(segment, exception.what());
138  continue;
139  }
140 
141  // Add the reconstruction into the ModelSeries
142  reconstructionDB.push_back(reconstruction);
143  }
144 
145  if(reconstructionDB.empty())
146  {
147  throw ::fwGdcmIO::exception::Failed("Unable to read the Surface Segmentation: no segments have been found.");
148  }
149 
150  // Update the reconstruction DB
151  m_object->setReconstructionDB(reconstructionDB);
152 
153 }
154 
155 //------------------------------------------------------------------------------
156 
157 std::string getStructureTypeFromSegmentIdentification(const ::gdcm::SmartPointer< ::gdcm::Segment >& segment,
158  const ::fwGdcmIO::helper::SegmentedPropertyRegistry& registry)
159 {
160  // Lambda used to format single entry
161  const auto format = [&](const ::gdcm::SegmentHelper::BasicCodedEntry& entry) -> std::string
162  {
163  if(!entry.IsEmpty())
164  {
165  return "(" + ::gdcm::LOComp::Trim(entry.CV.c_str()) +
166  ";" + ::gdcm::LOComp::Trim(entry.CSD.c_str()) +
167  ";" + ::gdcm::LOComp::Trim(entry.CM.c_str()) + ")";
168  }
169 
170  return "";
171  };
172 
173  // Lambda used to format multiple entries
174  const auto formatVector = [&](const std::vector< ::gdcm::SegmentHelper::BasicCodedEntry >& entries) -> std::string
175  {
176  std::string result;
177 
178  for(const auto& entry : entries)
179  {
180  result += format(entry);
181  }
182 
183  return result;
184  };
185 
186  // Retrieve Structure Type from segment identification
187  return registry.getStructureType(format(segment->GetPropertyType()),
188  format(segment->GetPropertyCategory()),
189  formatVector(segment->GetPropertyTypeModifiers()),
190  format(segment->GetAnatomicRegion()),
191  formatVector(segment->GetAnatomicRegionModifiers()));
192 
193 }
194 
195 //------------------------------------------------------------------------------
196 
197 void Surface::readSurfaceSegmentationModule(const ::fwData::Reconstruction::sptr& reconstruction,
198  const ::gdcm::SmartPointer< ::gdcm::Segment >& segment,
199  const ::gdcm::Item& segmentItem)
200 {
201  // Retrieve segment's dataset
202  const ::gdcm::DataSet& segmentDataset = segmentItem.GetNestedDataSet();
203 
204  // Organ Name - Segment Label (0x0062,0x0005) - Type 1
205  std::string organName = segment->GetSegmentLabel();
206  organName = ::gdcm::LOComp::Trim(organName.c_str());
207  reconstruction->setOrganName(organName);
208 
209  // Structure Type from Private Tag (0x5649,0x1000)
210  const ::gdcm::Tag structureTypeTag(0x5649, 0x1000);
211  auto privateCreator = ::gdcm::LOComp::Trim(segmentDataset.GetPrivateCreator(structureTypeTag).c_str());
212  if(segmentDataset.FindDataElement(structureTypeTag))
213  {
214  const auto structureType = ::fwGdcmIO::helper::DicomDataReader::getTagValue< 0x5649, 0x1000 >(segmentDataset);
215  reconstruction->setStructureType(structureType);
216  }
217  // Structure Type from Segment Identification
218  else
219  {
220  const auto structureType = getStructureTypeFromSegmentIdentification(segment, m_segmentedPropertyRegistry);
221  if(structureType.empty())
222  {
223  const std::string msg = "Unable to retrieve structure type from segment identification "
224  "for segment '" + organName + "'.";
225 
226  SLM_WARN_IF(msg, !m_logger);
227  if(m_logger)
228  {
229  m_logger->warning(msg);
230  }
231  }
232  }
233 
234  // Computed Mask Volume (0x5649, 0x1001)
235  const ::gdcm::Tag computedMaskVolumeTag(0x5649, 0x1001);
236  privateCreator = ::gdcm::LOComp::Trim(segmentDataset.GetPrivateCreator(computedMaskVolumeTag).c_str());
237  if(segmentDataset.FindDataElement(computedMaskVolumeTag))
238  {
239  ::gdcm::Attribute< 0x5649, 0x1001, ::gdcm::VR::OD, ::gdcm::VM::VM1 > attribute;
240  attribute.SetFromDataSet(segmentDataset);
241  const double volume = attribute.GetValue();
242  reconstruction->setComputedMaskVolume(volume);
243  }
244  else
245  {
246  reconstruction->setComputedMaskVolume(::fwData::Reconstruction::s_NO_COMPUTED_MASK_VOLUME);
247  }
248 
249 }
250 
251 //------------------------------------------------------------------------------
252 
253 void Surface::readSurfaceMeshModule(const ::fwData::Reconstruction::sptr& reconstruction,
254  const ::gdcm::SmartPointer< ::gdcm::Surface >& surface)
255 {
256  // Create material
257  ::fwData::Material::sptr material = fwData::Material::New();
258 
259  // Convert CIE Lab to RGBA
260  const unsigned short* lab = surface->GetRecommendedDisplayCIELabValue();
261  ::gdcm::SurfaceHelper::ColorArray CIELab(lab, lab + sizeof(lab) / sizeof(unsigned short));
262  std::vector<float> colorVector = ::gdcm::SurfaceHelper::RecommendedDisplayCIELabToRGB(CIELab, 1);
263 
264  // Recommended Presentation Opacity
265  colorVector.push_back(surface->GetRecommendedPresentationOpacity());
266 
267  // Adapt color to material
268  ::fwData::Color::ColorArray rgba;
269  ::boost::algorithm::clamp_range(colorVector.begin(), colorVector.end(), rgba.begin(), 0.f, 1.f);
270 
271  // Set reconstruction's visibility
272  const double epsilon = 1e-3;
273  reconstruction->setIsVisible(rgba[3] > epsilon);
274 
275  // Set reconstruction's color
276  ::fwData::Color::sptr color = ::fwData::Color::New();
277  color->setRGBA( rgba );
278  material->setDiffuse(color);
279  OSLM_TRACE("RGBA color : " << rgba[0]<<" "<< rgba[1]<<" "<< rgba[2]<<" "<< rgba[3]);
280 
281  // Recommended Presentation Type
282  const auto representationMode =
283  ::fwGdcmIO::helper::DicomDataTools::convertToRepresentationMode(surface->GetRecommendedPresentationType());
284  material->setRepresentationMode(representationMode);
285 
286  // Manifold
287  if (surface->GetManifold() == ::gdcm::Surface::YES)
288  {
289  throw ::fwGdcmIO::exception::Failed("Manifold meshes are not supported by the selected reader.");
290  }
291 
292  // Mesh Primitive
293  ::gdcm::SmartPointer< ::gdcm::MeshPrimitive > meshPrimitive = surface->GetMeshPrimitive();
294  if (meshPrimitive->GetPrimitiveType() != ::gdcm::MeshPrimitive::TRIANGLE)
295  {
296  throw ::fwGdcmIO::exception::Failed("The primitive type is not supported by the selected reader.");
297  }
298 
299  // Point Coordinates Data
300  const ::gdcm::ByteValue* pointCoordinates = surface->GetPointCoordinatesData().GetByteValue();
301  if(!pointCoordinates || !pointCoordinates->GetPointer())
302  {
303  throw ::fwGdcmIO::exception::Failed("No point coordinates data found.");
304  }
305 
306  // Compute number of coordinates
307  const auto pointCoordinatesSize = pointCoordinates->GetLength() / sizeof(float);
308  if((pointCoordinatesSize / 3) != surface->GetNumberOfSurfacePoints())
309  {
310  throw ::fwGdcmIO::exception::Failed("Point coordinates data are corrupted.");
311  }
312 
313  // Surface Points Normals
314  const ::gdcm::ByteValue* normalCoordinates = surface->GetVectorCoordinateData().GetByteValue();
315  const char* normalCoordinatesPointer = nullptr;
316  if (!surface->GetVectorCoordinateData().IsEmpty())
317  {
318  // Check that the surface contains normals
319  if(!normalCoordinates || !normalCoordinates->GetPointer())
320  {
321  throw ::fwGdcmIO::exception::Failed("No normal coordinates data found.");
322  }
323 
324  normalCoordinatesPointer = normalCoordinates->GetPointer();
325 
326  // Compute number of normal coordinates
327  const unsigned long normalCoordinateSize = normalCoordinates->GetLength() / sizeof(float);
328  if ((normalCoordinateSize / 3) != surface->GetNumberOfVectors() || normalCoordinateSize != pointCoordinatesSize)
329  {
330  throw ::fwGdcmIO::exception::Failed("Normal coordinates data are corrupted.");
331  }
332  }
333 
334  // Triangle Point Index List
335  const ::gdcm::ByteValue* pointIndices = meshPrimitive->GetPrimitiveData().GetByteValue();
336  if (!pointIndices || !pointIndices->GetPointer())
337  {
338  throw ::fwGdcmIO::exception::Failed("No triangle point index list found.");
339  }
340 
341  // Get number of primitives
342  const unsigned long indexSize = pointIndices->GetLength() / sizeof(uint32_t);
343 
344  // Create a new Mesh
345  ::fwGdcmIO::container::DicomSurface surfaceContainer(reinterpret_cast<const float*>(pointCoordinates->GetPointer()),
346  pointCoordinatesSize,
347  reinterpret_cast<const uint32_t*>(pointIndices->GetPointer()),
348  indexSize,
349  reinterpret_cast<const float*>(normalCoordinatesPointer));
350 
351  // Set the reconstruction
352  reconstruction->setMaterial( material );
353  reconstruction->setMesh( surfaceContainer.convertToData() );
354 
355 }
356 
357 //------------------------------------------------------------------------------
358 
359 } // namespace ie
360 } // namespace reader
361 } // namespace fwGdcmIO
#define SPTR(_cls_)
This class defines one surface mesh item in order to transform into DICOM/FW4SPL form.
Contains fwAtomsFilter::registry details.
static FWGDCMIO_API::fwData::Material::RepresentationType convertToRepresentationMode(::gdcm::Surface::VIEWType presentationType)
Convert a surface recommended presentation type (DICOM) into representation mode (FW4SPL).
Namespace containing medical data.
#define OSLM_TRACE(message)
Definition: spyLog.hpp:230
The namespace fwGdcmIO contains reader, writer and helper for dicom data.
static FWDATA_API const double s_NO_COMPUTED_MASK_VOLUME
Constant to inform that mask volume has not been computed yet.
FWGDCMIO_API Surface(const std::shared_ptr< const ::fwMedData::DicomSeries > &dicomSeries, const std::shared_ptr< ::gdcm::Reader > &reader, const std::shared_ptr< ::fwGdcmIO::container::DicomInstance > &instance, const ::fwMedData::ModelSeries::sptr &series, const ::fwLog::Logger::sptr &logger=nullptr, ProgressCallback progress=nullptr, CancelRequestedCallback cancel=nullptr)
Constructor.
#define SLM_WARN_IF(message, cond)
Definition: spyLog.hpp:265