Mercurial > hg > orthanc-stone
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) |