comparison Samples/Sdl/Loader.cpp @ 669:3805ffa2833d

cont
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 15 May 2019 18:35:43 +0200
parents e9339f2b5de7
children 6f10f9a6676a
comparison
equal deleted inserted replaced
668:6e13c7f98168 669:3805ffa2833d
1141 1141
1142 class DicomInstanceParameters : 1142 class DicomInstanceParameters :
1143 public Orthanc::IDynamicObject /* to be used as a payload of SlicesSorter */ 1143 public Orthanc::IDynamicObject /* to be used as a payload of SlicesSorter */
1144 { 1144 {
1145 private: 1145 private:
1146 Orthanc::DicomImageInformation imageInformation_; 1146 struct Data // Struct to ease the copy constructor
1147 OrthancStone::SopClassUid sopClassUid_; 1147 {
1148 double thickness_; 1148 Orthanc::DicomImageInformation imageInformation_;
1149 double pixelSpacingX_; 1149 OrthancStone::SopClassUid sopClassUid_;
1150 double pixelSpacingY_; 1150 double thickness_;
1151 OrthancStone::CoordinateSystem3D geometry_; 1151 double pixelSpacingX_;
1152 OrthancStone::Vector frameOffsets_; 1152 double pixelSpacingY_;
1153 bool isColor_; 1153 OrthancStone::CoordinateSystem3D geometry_;
1154 bool hasRescale_; 1154 OrthancStone::Vector frameOffsets_;
1155 double rescaleOffset_; 1155 bool isColor_;
1156 double rescaleSlope_; 1156 bool hasRescale_;
1157 bool hasDefaultWindowing_; 1157 double rescaleOffset_;
1158 float defaultWindowingCenter_; 1158 double rescaleSlope_;
1159 float defaultWindowingWidth_; 1159 bool hasDefaultWindowing_;
1160 Orthanc::PixelFormat expectedPixelFormat_; 1160 float defaultWindowingCenter_;
1161 1161 float defaultWindowingWidth_;
1162 void ComputeDoseOffsets(const Orthanc::DicomMap& dicom) 1162 Orthanc::PixelFormat expectedPixelFormat_;
1163 { 1163
1164 // http://dicom.nema.org/medical/Dicom/2016a/output/chtml/part03/sect_C.8.8.3.2.html 1164 void ComputeDoseOffsets(const Orthanc::DicomMap& dicom)
1165 1165 {
1166 { 1166 // http://dicom.nema.org/medical/Dicom/2016a/output/chtml/part03/sect_C.8.8.3.2.html
1167 std::string increment; 1167
1168 1168 {
1169 if (dicom.CopyToString(increment, Orthanc::DICOM_TAG_FRAME_INCREMENT_POINTER, false)) 1169 std::string increment;
1170 { 1170
1171 Orthanc::Toolbox::ToUpperCase(increment); 1171 if (dicom.CopyToString(increment, Orthanc::DICOM_TAG_FRAME_INCREMENT_POINTER, false))
1172 if (increment != "3004,000C") // This is the "Grid Frame Offset Vector" tag
1173 { 1172 {
1174 LOG(ERROR) << "RT-DOSE: Bad value for the \"FrameIncrementPointer\" tag"; 1173 Orthanc::Toolbox::ToUpperCase(increment);
1175 return; 1174 if (increment != "3004,000C") // This is the "Grid Frame Offset Vector" tag
1175 {
1176 LOG(ERROR) << "RT-DOSE: Bad value for the \"FrameIncrementPointer\" tag";
1177 return;
1178 }
1176 } 1179 }
1177 } 1180 }
1178 } 1181
1179 1182 if (!OrthancStone::LinearAlgebra::ParseVector(frameOffsets_, dicom, Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR) ||
1180 if (!OrthancStone::LinearAlgebra::ParseVector(frameOffsets_, dicom, Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR) || 1183 frameOffsets_.size() < imageInformation_.GetNumberOfFrames())
1181 frameOffsets_.size() < imageInformation_.GetNumberOfFrames()) 1184 {
1182 { 1185 LOG(ERROR) << "RT-DOSE: No information about the 3D location of some slice(s)";
1183 LOG(ERROR) << "RT-DOSE: No information about the 3D location of some slice(s)"; 1186 frameOffsets_.clear();
1184 frameOffsets_.clear(); 1187 }
1185 } 1188 else
1186 else 1189 {
1187 { 1190 if (frameOffsets_.size() >= 2)
1188 if (frameOffsets_.size() >= 2)
1189 {
1190 thickness_ = frameOffsets_[1] - frameOffsets_[0];
1191
1192 if (thickness_ < 0)
1193 { 1191 {
1194 thickness_ = -thickness_; 1192 thickness_ = frameOffsets_[1] - frameOffsets_[0];
1193
1194 if (thickness_ < 0)
1195 {
1196 thickness_ = -thickness_;
1197 }
1195 } 1198 }
1196 } 1199 }
1197 } 1200 }
1198 } 1201
1202 Data(const Orthanc::DicomMap& dicom) :
1203 imageInformation_(dicom)
1204 {
1205 if (imageInformation_.GetNumberOfFrames() <= 0)
1206 {
1207 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
1208 }
1209
1210 std::string s;
1211 if (!dicom.CopyToString(s, Orthanc::DICOM_TAG_SOP_CLASS_UID, false))
1212 {
1213 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
1214 }
1215 else
1216 {
1217 sopClassUid_ = OrthancStone::StringToSopClassUid(s);
1218 }
1219
1220 if (!dicom.ParseDouble(thickness_, Orthanc::DICOM_TAG_SLICE_THICKNESS))
1221 {
1222 thickness_ = 100.0 * std::numeric_limits<double>::epsilon();
1223 }
1224
1225 OrthancStone::GeometryToolbox::GetPixelSpacing(pixelSpacingX_, pixelSpacingY_, dicom);
1226
1227 std::string position, orientation;
1228 if (dicom.CopyToString(position, Orthanc::DICOM_TAG_IMAGE_POSITION_PATIENT, false) &&
1229 dicom.CopyToString(orientation, Orthanc::DICOM_TAG_IMAGE_ORIENTATION_PATIENT, false))
1230 {
1231 geometry_ = OrthancStone::CoordinateSystem3D(position, orientation);
1232 }
1233
1234 if (sopClassUid_ == OrthancStone::SopClassUid_RTDose)
1235 {
1236 ComputeDoseOffsets(dicom);
1237 }
1238
1239 isColor_ = (imageInformation_.GetPhotometricInterpretation() != Orthanc::PhotometricInterpretation_Monochrome1 &&
1240 imageInformation_.GetPhotometricInterpretation() != Orthanc::PhotometricInterpretation_Monochrome2);
1241
1242 double doseGridScaling;
1243
1244 if (dicom.ParseDouble(rescaleOffset_, Orthanc::DICOM_TAG_RESCALE_INTERCEPT) &&
1245 dicom.ParseDouble(rescaleSlope_, Orthanc::DICOM_TAG_RESCALE_SLOPE))
1246 {
1247 hasRescale_ = true;
1248 }
1249 else if (dicom.ParseDouble(doseGridScaling, Orthanc::DICOM_TAG_DOSE_GRID_SCALING))
1250 {
1251 hasRescale_ = true;
1252 rescaleOffset_ = 0;
1253 rescaleSlope_ = doseGridScaling;
1254 }
1255 else
1256 {
1257 hasRescale_ = false;
1258 }
1259
1260 OrthancStone::Vector c, w;
1261 if (OrthancStone::LinearAlgebra::ParseVector(c, dicom, Orthanc::DICOM_TAG_WINDOW_CENTER) &&
1262 OrthancStone::LinearAlgebra::ParseVector(w, dicom, Orthanc::DICOM_TAG_WINDOW_WIDTH) &&
1263 c.size() > 0 &&
1264 w.size() > 0)
1265 {
1266 hasDefaultWindowing_ = true;
1267 defaultWindowingCenter_ = static_cast<float>(c[0]);
1268 defaultWindowingWidth_ = static_cast<float>(w[0]);
1269 }
1270 else
1271 {
1272 hasDefaultWindowing_ = false;
1273 }
1274
1275 if (sopClassUid_ == OrthancStone::SopClassUid_RTDose)
1276 {
1277 switch (imageInformation_.GetBitsStored())
1278 {
1279 case 16:
1280 expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale16;
1281 break;
1282
1283 case 32:
1284 expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale32;
1285 break;
1286
1287 default:
1288 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
1289 }
1290 }
1291 else if (isColor_)
1292 {
1293 expectedPixelFormat_ = Orthanc::PixelFormat_RGB24;
1294 }
1295 else if (imageInformation_.IsSigned())
1296 {
1297 expectedPixelFormat_ = Orthanc::PixelFormat_SignedGrayscale16;
1298 }
1299 else
1300 {
1301 expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale16;
1302 }
1303 }
1304
1305 OrthancStone::CoordinateSystem3D GetFrameGeometry(unsigned int frame) const
1306 {
1307 if (frame == 0)
1308 {
1309 return geometry_;
1310 }
1311 else if (frame >= imageInformation_.GetNumberOfFrames())
1312 {
1313 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
1314 }
1315 else if (sopClassUid_ == OrthancStone::SopClassUid_RTDose)
1316 {
1317 if (frame >= frameOffsets_.size())
1318 {
1319 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
1320 }
1321
1322 return OrthancStone::CoordinateSystem3D(
1323 geometry_.GetOrigin() + frameOffsets_[frame] * geometry_.GetNormal(),
1324 geometry_.GetAxisX(),
1325 geometry_.GetAxisY());
1326 }
1327 else
1328 {
1329 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
1330 }
1331 }
1332
1333 // TODO - Is this necessary?
1334 bool FrameContainsPlane(unsigned int frame,
1335 const OrthancStone::CoordinateSystem3D& plane) const
1336 {
1337 if (frame >= imageInformation_.GetNumberOfFrames())
1338 {
1339 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
1340 }
1341
1342 OrthancStone::CoordinateSystem3D tmp = geometry_;
1343
1344 if (frame != 0)
1345 {
1346 tmp = GetFrameGeometry(frame);
1347 }
1348
1349 double distance;
1350
1351 return (OrthancStone::CoordinateSystem3D::GetDistance(distance, tmp, plane) &&
1352 distance <= thickness_ / 2.0);
1353 }
1354 };
1355
1356 Data data_;
1357
1199 1358
1200 public: 1359 public:
1360 DicomInstanceParameters(const DicomInstanceParameters& other) :
1361 data_(other.data_)
1362 {
1363 }
1364
1365
1201 DicomInstanceParameters(const Orthanc::DicomMap& dicom) : 1366 DicomInstanceParameters(const Orthanc::DicomMap& dicom) :
1202 imageInformation_(dicom) 1367 data_(dicom)
1203 { 1368 {
1204 if (imageInformation_.GetNumberOfFrames() <= 0)
1205 {
1206 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
1207 }
1208
1209 std::string s;
1210 if (!dicom.CopyToString(s, Orthanc::DICOM_TAG_SOP_CLASS_UID, false))
1211 {
1212 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
1213 }
1214 else
1215 {
1216 sopClassUid_ = OrthancStone::StringToSopClassUid(s);
1217 }
1218
1219 if (!dicom.ParseDouble(thickness_, Orthanc::DICOM_TAG_SLICE_THICKNESS))
1220 {
1221 thickness_ = 100.0 * std::numeric_limits<double>::epsilon();
1222 }
1223
1224 OrthancStone::GeometryToolbox::GetPixelSpacing(pixelSpacingX_, pixelSpacingY_, dicom);
1225
1226 std::string position, orientation;
1227 if (dicom.CopyToString(position, Orthanc::DICOM_TAG_IMAGE_POSITION_PATIENT, false) &&
1228 dicom.CopyToString(orientation, Orthanc::DICOM_TAG_IMAGE_ORIENTATION_PATIENT, false))
1229 {
1230 geometry_ = OrthancStone::CoordinateSystem3D(position, orientation);
1231 }
1232
1233 if (sopClassUid_ == OrthancStone::SopClassUid_RTDose)
1234 {
1235 ComputeDoseOffsets(dicom);
1236 }
1237
1238 isColor_ = (imageInformation_.GetPhotometricInterpretation() != Orthanc::PhotometricInterpretation_Monochrome1 &&
1239 imageInformation_.GetPhotometricInterpretation() != Orthanc::PhotometricInterpretation_Monochrome2);
1240
1241 double doseGridScaling;
1242
1243 if (dicom.ParseDouble(rescaleOffset_, Orthanc::DICOM_TAG_RESCALE_INTERCEPT) &&
1244 dicom.ParseDouble(rescaleSlope_, Orthanc::DICOM_TAG_RESCALE_SLOPE))
1245 {
1246 hasRescale_ = true;
1247 }
1248 else if (dicom.ParseDouble(doseGridScaling, Orthanc::DICOM_TAG_DOSE_GRID_SCALING))
1249 {
1250 hasRescale_ = true;
1251 rescaleOffset_ = 0;
1252 rescaleSlope_ = doseGridScaling;
1253 }
1254 else
1255 {
1256 hasRescale_ = false;
1257 }
1258
1259 OrthancStone::Vector c, w;
1260 if (OrthancStone::LinearAlgebra::ParseVector(c, dicom, Orthanc::DICOM_TAG_WINDOW_CENTER) &&
1261 OrthancStone::LinearAlgebra::ParseVector(w, dicom, Orthanc::DICOM_TAG_WINDOW_WIDTH) &&
1262 c.size() > 0 &&
1263 w.size() > 0)
1264 {
1265 hasDefaultWindowing_ = true;
1266 defaultWindowingCenter_ = static_cast<float>(c[0]);
1267 defaultWindowingWidth_ = static_cast<float>(w[0]);
1268 }
1269 else
1270 {
1271 hasDefaultWindowing_ = false;
1272 }
1273
1274 if (sopClassUid_ == OrthancStone::SopClassUid_RTDose)
1275 {
1276 switch (imageInformation_.GetBitsStored())
1277 {
1278 case 16:
1279 expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale16;
1280 break;
1281
1282 case 32:
1283 expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale32;
1284 break;
1285
1286 default:
1287 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
1288 }
1289 }
1290 else if (isColor_)
1291 {
1292 expectedPixelFormat_ = Orthanc::PixelFormat_RGB24;
1293 }
1294 else if (imageInformation_.IsSigned())
1295 {
1296 expectedPixelFormat_ = Orthanc::PixelFormat_SignedGrayscale16;
1297 }
1298 else
1299 {
1300 expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale16;
1301 }
1302 } 1369 }
1303 1370
1304 const Orthanc::DicomImageInformation& GetImageInformation() const 1371 const Orthanc::DicomImageInformation& GetImageInformation() const
1305 { 1372 {
1306 return imageInformation_; 1373 return data_.imageInformation_;
1307 } 1374 }
1308 1375
1309 OrthancStone::SopClassUid GetSopClassUid() const 1376 OrthancStone::SopClassUid GetSopClassUid() const
1310 { 1377 {
1311 return sopClassUid_; 1378 return data_.sopClassUid_;
1312 } 1379 }
1313 1380
1314 double GetThickness() const 1381 double GetThickness() const
1315 { 1382 {
1316 return thickness_; 1383 return data_.thickness_;
1317 } 1384 }
1318 1385
1319 double GetPixelSpacingX() const 1386 double GetPixelSpacingX() const
1320 { 1387 {
1321 return pixelSpacingX_; 1388 return data_.pixelSpacingX_;
1322 } 1389 }
1323 1390
1324 double GetPixelSpacingY() const 1391 double GetPixelSpacingY() const
1325 { 1392 {
1326 return pixelSpacingY_; 1393 return data_.pixelSpacingY_;
1327 } 1394 }
1328 1395
1329 const OrthancStone::CoordinateSystem3D& GetGeometry() const 1396 const OrthancStone::CoordinateSystem3D& GetGeometry() const
1330 { 1397 {
1331 return geometry_; 1398 return data_.geometry_;
1332 } 1399 }
1333 1400
1334 OrthancStone::CoordinateSystem3D GetFrameGeometry(unsigned int frame) const 1401 OrthancStone::CoordinateSystem3D GetFrameGeometry(unsigned int frame) const
1335 { 1402 {
1336 if (frame == 0) 1403 return data_.GetFrameGeometry(frame);
1337 {
1338 return geometry_;
1339 }
1340 else if (frame >= imageInformation_.GetNumberOfFrames())
1341 {
1342 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
1343 }
1344 else if (sopClassUid_ == OrthancStone::SopClassUid_RTDose)
1345 {
1346 if (frame >= frameOffsets_.size())
1347 {
1348 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
1349 }
1350
1351 return OrthancStone::CoordinateSystem3D(
1352 geometry_.GetOrigin() + frameOffsets_[frame] * geometry_.GetNormal(),
1353 geometry_.GetAxisX(),
1354 geometry_.GetAxisY());
1355 }
1356 else
1357 {
1358 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
1359 }
1360 } 1404 }
1361 1405
1362 // TODO - Is this necessary? 1406 // TODO - Is this necessary?
1363 bool FrameContainsPlane(unsigned int frame, 1407 bool FrameContainsPlane(unsigned int frame,
1364 const OrthancStone::CoordinateSystem3D& plane) const 1408 const OrthancStone::CoordinateSystem3D& plane) const
1365 { 1409 {
1366 if (frame >= imageInformation_.GetNumberOfFrames()) 1410 return data_.FrameContainsPlane(frame, plane);
1367 {
1368 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
1369 }
1370
1371 OrthancStone::CoordinateSystem3D tmp = geometry_;
1372
1373 if (frame != 0)
1374 {
1375 tmp = GetFrameGeometry(frame);
1376 }
1377
1378 double distance;
1379
1380 return (OrthancStone::CoordinateSystem3D::GetDistance(distance, tmp, plane) &&
1381 distance <= thickness_ / 2.0);
1382 } 1411 }
1383 1412
1384 bool IsColor() const 1413 bool IsColor() const
1385 { 1414 {
1386 return isColor_; 1415 return data_.isColor_;
1387 } 1416 }
1388 1417
1389 bool HasRescale() const 1418 bool HasRescale() const
1390 { 1419 {
1391 return hasRescale_; 1420 return data_.hasRescale_;
1392 } 1421 }
1393 1422
1394 double GetRescaleOffset() const 1423 double GetRescaleOffset() const
1395 { 1424 {
1396 if (hasRescale_) 1425 if (data_.hasRescale_)
1397 { 1426 {
1398 return rescaleOffset_; 1427 return data_.rescaleOffset_;
1399 } 1428 }
1400 else 1429 else
1401 { 1430 {
1402 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); 1431 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
1403 } 1432 }
1404 } 1433 }
1405 1434
1406 double GetRescaleSlope() const 1435 double GetRescaleSlope() const
1407 { 1436 {
1408 if (hasRescale_) 1437 if (data_.hasRescale_)
1409 { 1438 {
1410 return rescaleSlope_; 1439 return data_.rescaleSlope_;
1411 } 1440 }
1412 else 1441 else
1413 { 1442 {
1414 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); 1443 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
1415 } 1444 }
1416 } 1445 }
1417 1446
1418 bool HasDefaultWindowing() const 1447 bool HasDefaultWindowing() const
1419 { 1448 {
1420 return hasDefaultWindowing_; 1449 return data_.hasDefaultWindowing_;
1421 } 1450 }
1422 1451
1423 float GetDefaultWindowingCenter() const 1452 float GetDefaultWindowingCenter() const
1424 { 1453 {
1425 if (hasDefaultWindowing_) 1454 if (data_.hasDefaultWindowing_)
1426 { 1455 {
1427 return defaultWindowingCenter_; 1456 return data_.defaultWindowingCenter_;
1428 } 1457 }
1429 else 1458 else
1430 { 1459 {
1431 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); 1460 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
1432 } 1461 }
1433 } 1462 }
1434 1463
1435 float GetDefaultWindowingWidth() const 1464 float GetDefaultWindowingWidth() const
1436 { 1465 {
1437 if (hasDefaultWindowing_) 1466 if (data_.hasDefaultWindowing_)
1438 { 1467 {
1439 return defaultWindowingWidth_; 1468 return data_.defaultWindowingWidth_;
1440 } 1469 }
1441 else 1470 else
1442 { 1471 {
1443 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); 1472 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
1444 } 1473 }
1445 } 1474 }
1446 1475
1447 Orthanc::PixelFormat GetExpectedPixelFormat() const 1476 Orthanc::PixelFormat GetExpectedPixelFormat() const
1448 { 1477 {
1449 return expectedPixelFormat_; 1478 return data_.expectedPixelFormat_;
1450 } 1479 }
1451 }; 1480 };
1452 1481
1453 1482
1454 class VolumeImage : public boost::noncopyable 1483 class DicomVolumeImage : public boost::noncopyable
1455 { 1484 {
1456 private: 1485 private:
1457 std::auto_ptr<OrthancStone::SlicesSorter> slices_;
1458 std::auto_ptr<OrthancStone::ImageBuffer3D> image_; 1486 std::auto_ptr<OrthancStone::ImageBuffer3D> image_;
1459 1487 std::vector<DicomInstanceParameters*> slices_;
1460 const DicomInstanceParameters& GetSliceParameters(size_t index) const 1488
1461 { 1489 static const DicomInstanceParameters&
1462 return dynamic_cast<const DicomInstanceParameters&>(slices_->GetSlicePayload(index)); 1490 GetSliceParameters(const OrthancStone::SlicesSorter& slices,
1463 } 1491 size_t index)
1464 1492 {
1465 void CheckSlice(size_t index, 1493 return dynamic_cast<const DicomInstanceParameters&>(slices.GetSlicePayload(index));
1466 const OrthancStone::CoordinateSystem3D& reference, 1494 }
1467 const DicomInstanceParameters& a) const 1495
1468 { 1496 static void CheckSlice(const OrthancStone::SlicesSorter& slices,
1469 const OrthancStone::CoordinateSystem3D& slice = slices_->GetSliceGeometry(index); 1497 size_t index,
1470 const DicomInstanceParameters& b = GetSliceParameters(index); 1498 const OrthancStone::CoordinateSystem3D& reference,
1499 const DicomInstanceParameters& a)
1500 {
1501 const OrthancStone::CoordinateSystem3D& slice = slices.GetSliceGeometry(index);
1502 const DicomInstanceParameters& b = GetSliceParameters(slices, index);
1471 1503
1472 if (!OrthancStone::GeometryToolbox::IsParallel(reference.GetNormal(), slice.GetNormal())) 1504 if (!OrthancStone::GeometryToolbox::IsParallel(reference.GetNormal(), slice.GetNormal()))
1473 { 1505 {
1474 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadGeometry, 1506 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadGeometry,
1475 "A slice in the volume image is not parallel to the others"); 1507 "A slice in the volume image is not parallel to the others");
1495 "The pixel spacing of the slices change across the volume image"); 1527 "The pixel spacing of the slices change across the volume image");
1496 } 1528 }
1497 } 1529 }
1498 1530
1499 1531
1500 void CheckVolume() 1532 static void CheckVolume(const OrthancStone::SlicesSorter& slices)
1501 { 1533 {
1502 if (slices_->GetSlicesCount() != 0) 1534 for (size_t i = 0; i < slices.GetSlicesCount(); i++)
1503 { 1535 {
1504 const OrthancStone::CoordinateSystem3D& reference = slices_->GetSliceGeometry(0); 1536 if (GetSliceParameters(slices, i).GetImageInformation().GetNumberOfFrames() != 1)
1505 const DicomInstanceParameters& dicom = GetSliceParameters(0); 1537 {
1506 1538 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadGeometry,
1507 for (size_t i = 1; i < slices_->GetSlicesCount(); i++) 1539 "This class does not support multi-frame images");
1508 { 1540 }
1509 CheckSlice(i, reference, dicom); 1541 }
1510 } 1542
1543 if (slices.GetSlicesCount() != 0)
1544 {
1545 const OrthancStone::CoordinateSystem3D& reference = slices.GetSliceGeometry(0);
1546 const DicomInstanceParameters& dicom = GetSliceParameters(slices, 0);
1547
1548 for (size_t i = 1; i < slices.GetSlicesCount(); i++)
1549 {
1550 CheckSlice(slices, i, reference, dicom);
1551 }
1552 }
1553 }
1554
1555
1556 void Clear()
1557 {
1558 image_.reset();
1559
1560 for (size_t i = 0; i < slices_.size(); i++)
1561 {
1562 assert(slices_[i] != NULL);
1563 delete slices_[i];
1511 } 1564 }
1512 } 1565 }
1513 1566
1514 1567
1515 public: 1568 public:
1516 VolumeImage() 1569 DicomVolumeImage()
1517 { 1570 {
1571 }
1572
1573 ~DicomVolumeImage()
1574 {
1575 Clear();
1518 } 1576 }
1519 1577
1520 // WARNING: The payload of "slices" must be of class "DicomInstanceParameters" 1578 // WARNING: The payload of "slices" must be of class "DicomInstanceParameters"
1521 void SetGeometry(OrthancStone::SlicesSorter* slices) // Takes ownership 1579 void SetGeometry(OrthancStone::SlicesSorter& slices)
1522 { 1580 {
1523 image_.reset(); 1581 Clear();
1524 slices_.reset(slices);
1525 1582
1526 if (!slices_->Sort()) 1583 if (!slices.Sort())
1527 { 1584 {
1528 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange, 1585 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange,
1529 "Cannot sort the 3D slices of a DICOM series"); 1586 "Cannot sort the 3D slices of a DICOM series");
1530 } 1587 }
1531 1588
1532 CheckVolume(); 1589 slices_.reserve(slices.GetSlicesCount());
1533 1590
1534 const double spacingZ = slices_->ComputeSpacingBetweenSlices(); 1591 for (size_t i = 0; i < slices.GetSlicesCount(); i++)
1592 {
1593 slices_.push_back(new DicomInstanceParameters(GetSliceParameters(slices, i)));
1594 }
1595
1596 CheckVolume(slices);
1597
1598 const double spacingZ = slices.ComputeSpacingBetweenSlices();
1535 LOG(INFO) << "Computed spacing between slices: " << spacingZ << "mm"; 1599 LOG(INFO) << "Computed spacing between slices: " << spacingZ << "mm";
1536 1600
1537 const DicomInstanceParameters& parameters = GetSliceParameters(0); 1601 const DicomInstanceParameters& parameters = GetSliceParameters(slices, 0);
1538 1602
1539 image_.reset(new OrthancStone::ImageBuffer3D(parameters.GetExpectedPixelFormat(), 1603 image_.reset(new OrthancStone::ImageBuffer3D(parameters.GetExpectedPixelFormat(),
1540 parameters.GetImageInformation().GetWidth(), 1604 parameters.GetImageInformation().GetWidth(),
1541 parameters.GetImageInformation().GetHeight(), 1605 parameters.GetImageInformation().GetHeight(),
1542 slices_->GetSlicesCount(), false /* don't compute range */)); 1606 slices.GetSlicesCount(), false /* don't compute range */));
1543 1607
1544 image_->SetAxialGeometry(slices_->GetSliceGeometry(0)); 1608 image_->SetAxialGeometry(slices.GetSliceGeometry(0));
1545 image_->SetVoxelDimensions(parameters.GetPixelSpacingX(), parameters.GetPixelSpacingY(), spacingZ); 1609 image_->SetVoxelDimensions(parameters.GetPixelSpacingX(), parameters.GetPixelSpacingY(), spacingZ);
1546 image_->Clear(); 1610 image_->Clear();
1547 } 1611 }
1548 1612
1549 bool IsGeometryReady() const 1613 bool IsGeometryReady() const
1550 { 1614 {
1551 return (image_.get() != NULL && 1615 return (image_.get() != NULL);
1552 slices_.get() != NULL);
1553 }
1554
1555 const OrthancStone::SlicesSorter& GetSlices() const
1556 {
1557 if (!IsGeometryReady())
1558 {
1559 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
1560 }
1561 else
1562 {
1563 return *slices_;
1564 }
1565 } 1616 }
1566 1617
1567 const OrthancStone::ImageBuffer3D& GetImage() const 1618 const OrthancStone::ImageBuffer3D& GetImage() const
1568 { 1619 {
1569 if (!IsGeometryReady()) 1620 if (!IsGeometryReady())
1615 throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); 1666 throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
1616 } 1667 }
1617 1668
1618 Json::Value::Members instances = value.getMemberNames(); 1669 Json::Value::Members instances = value.getMemberNames();
1619 1670
1620 std::auto_ptr<OrthancStone::SlicesSorter> slices(new OrthancStone::SlicesSorter); 1671 OrthancStone::SlicesSorter slices;
1621 1672
1622 for (size_t i = 0; i < instances.size(); i++) 1673 for (size_t i = 0; i < instances.size(); i++)
1623 { 1674 {
1624 Orthanc::DicomMap dicom; 1675 Orthanc::DicomMap dicom;
1625 dicom.FromDicomAsJson(value[instances[i]]); 1676 dicom.FromDicomAsJson(value[instances[i]]);
1626 1677
1627 std::auto_ptr<DicomInstanceParameters> instance(new DicomInstanceParameters(dicom)); 1678 std::auto_ptr<DicomInstanceParameters> instance(new DicomInstanceParameters(dicom));
1628 1679
1629 OrthancStone::CoordinateSystem3D geometry = instance->GetGeometry(); 1680 OrthancStone::CoordinateSystem3D geometry = instance->GetGeometry();
1630 slices->AddSlice(geometry, instance.release()); 1681 slices.AddSlice(geometry, instance.release());
1631 } 1682 }
1632 1683
1633 that_.image_.SetGeometry(slices.release()); 1684 that_.image_.SetGeometry(slices);
1634 } 1685 }
1635 }; 1686 };
1636 1687
1637 1688
1638 class LoadInstanceGeometryHandler : public MessageHandler 1689 class LoadInstanceGeometryHandler : public MessageHandler
1662 DicomInstanceParameters instance(dicom); 1713 DicomInstanceParameters instance(dicom);
1663 } 1714 }
1664 }; 1715 };
1665 1716
1666 1717
1667 bool active_; 1718 bool active_;
1668 VolumeImage image_; 1719 DicomVolumeImage image_;
1669 1720
1670 public: 1721 public:
1671 AxialVolumeOrthancLoader(OrthancStone::IObservable& oracle) : 1722 AxialVolumeOrthancLoader(OrthancStone::IObservable& oracle) :
1672 IObserver(oracle.GetBroker()), 1723 IObserver(oracle.GetBroker()),
1673 active_(false) 1724 active_(false)