Mercurial > hg > orthanc-stone
comparison Samples/Sdl/Loader.cpp @ 712:f334b098b243
IDicomVolumeSource
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Mon, 20 May 2019 14:56:03 +0200 |
parents | 70d1c28560b3 |
children | e63c8b9b7b02 |
comparison
equal
deleted
inserted
replaced
711:70d1c28560b3 | 712:f334b098b243 |
---|---|
17 * You should have received a copy of the GNU Affero General Public License | 17 * You should have received a copy of the GNU Affero General Public License |
18 * along with this program. If not, see <http://www.gnu.org/licenses/>. | 18 * along with this program. If not, see <http://www.gnu.org/licenses/>. |
19 **/ | 19 **/ |
20 | 20 |
21 // From Stone | 21 // From Stone |
22 #include "../../Framework/Loaders/BasicFetchingItemsSorter.h" | |
23 #include "../../Framework/Loaders/BasicFetchingStrategy.h" | |
22 #include "../../Framework/Messages/ICallable.h" | 24 #include "../../Framework/Messages/ICallable.h" |
23 #include "../../Framework/Messages/IMessage.h" | 25 #include "../../Framework/Messages/IMessage.h" |
24 #include "../../Framework/Messages/IObservable.h" | 26 #include "../../Framework/Messages/IObservable.h" |
25 #include "../../Framework/Messages/MessageBroker.h" | 27 #include "../../Framework/Messages/MessageBroker.h" |
28 #include "../../Framework/Scene2D/ColorTextureSceneLayer.h" | |
29 #include "../../Framework/Scene2D/FloatTextureSceneLayer.h" | |
30 #include "../../Framework/Scene2D/Scene2D.h" | |
26 #include "../../Framework/StoneInitialization.h" | 31 #include "../../Framework/StoneInitialization.h" |
27 #include "../../Framework/Toolbox/GeometryToolbox.h" | 32 #include "../../Framework/Toolbox/GeometryToolbox.h" |
28 #include "../../Framework/Toolbox/SlicesSorter.h" | 33 #include "../../Framework/Toolbox/SlicesSorter.h" |
29 #include "../../Framework/Volumes/ImageBuffer3D.h" | 34 #include "../../Framework/Volumes/ImageBuffer3D.h" |
30 #include "../../Framework/Scene2D/Scene2D.h" | |
31 #include "../../Framework/Loaders/BasicFetchingStrategy.h" | |
32 #include "../../Framework/Loaders/BasicFetchingItemsSorter.h" | |
33 | 35 |
34 // From Orthanc framework | 36 // From Orthanc framework |
35 #include <Core/Compression/GzipCompressor.h> | 37 #include <Core/Compression/GzipCompressor.h> |
36 #include <Core/Compression/ZlibCompressor.h> | 38 #include <Core/Compression/ZlibCompressor.h> |
37 #include <Core/DicomFormat/DicomArray.h> | 39 #include <Core/DicomFormat/DicomArray.h> |
812 double pixelSpacingY_; | 814 double pixelSpacingY_; |
813 OrthancStone::CoordinateSystem3D geometry_; | 815 OrthancStone::CoordinateSystem3D geometry_; |
814 OrthancStone::Vector frameOffsets_; | 816 OrthancStone::Vector frameOffsets_; |
815 bool isColor_; | 817 bool isColor_; |
816 bool hasRescale_; | 818 bool hasRescale_; |
817 double rescaleOffset_; | 819 double rescaleIntercept_; |
818 double rescaleSlope_; | 820 double rescaleSlope_; |
819 bool hasDefaultWindowing_; | 821 bool hasDefaultWindowing_; |
820 float defaultWindowingCenter_; | 822 float defaultWindowingCenter_; |
821 float defaultWindowingWidth_; | 823 float defaultWindowingWidth_; |
822 Orthanc::PixelFormat expectedPixelFormat_; | 824 Orthanc::PixelFormat expectedPixelFormat_; |
906 isColor_ = (imageInformation_.GetPhotometricInterpretation() != Orthanc::PhotometricInterpretation_Monochrome1 && | 908 isColor_ = (imageInformation_.GetPhotometricInterpretation() != Orthanc::PhotometricInterpretation_Monochrome1 && |
907 imageInformation_.GetPhotometricInterpretation() != Orthanc::PhotometricInterpretation_Monochrome2); | 909 imageInformation_.GetPhotometricInterpretation() != Orthanc::PhotometricInterpretation_Monochrome2); |
908 | 910 |
909 double doseGridScaling; | 911 double doseGridScaling; |
910 | 912 |
911 if (dicom.ParseDouble(rescaleOffset_, Orthanc::DICOM_TAG_RESCALE_INTERCEPT) && | 913 if (dicom.ParseDouble(rescaleIntercept_, Orthanc::DICOM_TAG_RESCALE_INTERCEPT) && |
912 dicom.ParseDouble(rescaleSlope_, Orthanc::DICOM_TAG_RESCALE_SLOPE)) | 914 dicom.ParseDouble(rescaleSlope_, Orthanc::DICOM_TAG_RESCALE_SLOPE)) |
913 { | 915 { |
914 hasRescale_ = true; | 916 hasRescale_ = true; |
915 } | 917 } |
916 else if (dicom.ParseDouble(doseGridScaling, Orthanc::DICOM_TAG_DOSE_GRID_SCALING)) | 918 else if (dicom.ParseDouble(doseGridScaling, Orthanc::DICOM_TAG_DOSE_GRID_SCALING)) |
917 { | 919 { |
918 hasRescale_ = true; | 920 hasRescale_ = true; |
919 rescaleOffset_ = 0; | 921 rescaleIntercept_ = 0; |
920 rescaleSlope_ = doseGridScaling; | 922 rescaleSlope_ = doseGridScaling; |
921 } | 923 } |
922 else | 924 else |
923 { | 925 { |
924 hasRescale_ = false; | 926 hasRescale_ = false; |
1016 double distance; | 1018 double distance; |
1017 | 1019 |
1018 return (OrthancStone::CoordinateSystem3D::GetDistance(distance, tmp, plane) && | 1020 return (OrthancStone::CoordinateSystem3D::GetDistance(distance, tmp, plane) && |
1019 distance <= thickness_ / 2.0); | 1021 distance <= thickness_ / 2.0); |
1020 } | 1022 } |
1023 | |
1024 | |
1025 void ApplyRescale(Orthanc::ImageAccessor& image, | |
1026 bool useDouble) const | |
1027 { | |
1028 if (image.GetFormat() != Orthanc::PixelFormat_Float32) | |
1029 { | |
1030 throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); | |
1031 } | |
1032 | |
1033 if (hasRescale_) | |
1034 { | |
1035 const unsigned int width = image.GetWidth(); | |
1036 const unsigned int height = image.GetHeight(); | |
1037 | |
1038 for (unsigned int y = 0; y < height; y++) | |
1039 { | |
1040 float* p = reinterpret_cast<float*>(image.GetRow(y)); | |
1041 | |
1042 if (useDouble) | |
1043 { | |
1044 // Slower, accurate implementation using double | |
1045 for (unsigned int x = 0; x < width; x++, p++) | |
1046 { | |
1047 double value = static_cast<double>(*p); | |
1048 *p = static_cast<float>(value * rescaleSlope_ + rescaleIntercept_); | |
1049 } | |
1050 } | |
1051 else | |
1052 { | |
1053 // Fast, approximate implementation using float | |
1054 for (unsigned int x = 0; x < width; x++, p++) | |
1055 { | |
1056 *p = (*p) * static_cast<float>(rescaleSlope_) + static_cast<float>(rescaleIntercept_); | |
1057 } | |
1058 } | |
1059 } | |
1060 } | |
1061 } | |
1021 }; | 1062 }; |
1063 | |
1022 | 1064 |
1023 Data data_; | 1065 Data data_; |
1024 | 1066 |
1025 | 1067 |
1026 public: | 1068 public: |
1109 bool HasRescale() const | 1151 bool HasRescale() const |
1110 { | 1152 { |
1111 return data_.hasRescale_; | 1153 return data_.hasRescale_; |
1112 } | 1154 } |
1113 | 1155 |
1114 double GetRescaleOffset() const | 1156 double GetRescaleIntercept() const |
1115 { | 1157 { |
1116 if (data_.hasRescale_) | 1158 if (data_.hasRescale_) |
1117 { | 1159 { |
1118 return data_.rescaleOffset_; | 1160 return data_.rescaleIntercept_; |
1119 } | 1161 } |
1120 else | 1162 else |
1121 { | 1163 { |
1122 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | 1164 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); |
1123 } | 1165 } |
1165 } | 1207 } |
1166 | 1208 |
1167 Orthanc::PixelFormat GetExpectedPixelFormat() const | 1209 Orthanc::PixelFormat GetExpectedPixelFormat() const |
1168 { | 1210 { |
1169 return data_.expectedPixelFormat_; | 1211 return data_.expectedPixelFormat_; |
1212 } | |
1213 | |
1214 | |
1215 OrthancStone::TextureBaseSceneLayer* CreateTexture(const Orthanc::ImageAccessor& source) const | |
1216 { | |
1217 assert(sizeof(float) == 4); | |
1218 | |
1219 Orthanc::PixelFormat sourceFormat = source.GetFormat(); | |
1220 | |
1221 if (sourceFormat != GetExpectedPixelFormat()) | |
1222 { | |
1223 throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); | |
1224 } | |
1225 | |
1226 if (sourceFormat == Orthanc::PixelFormat_RGB24) | |
1227 { | |
1228 // This is the case of a color image. No conversion has to be done. | |
1229 return new OrthancStone::ColorTextureSceneLayer(source); | |
1230 } | |
1231 else | |
1232 { | |
1233 if (sourceFormat != Orthanc::PixelFormat_Grayscale16 && | |
1234 sourceFormat != Orthanc::PixelFormat_Grayscale32 && | |
1235 sourceFormat != Orthanc::PixelFormat_SignedGrayscale16) | |
1236 { | |
1237 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); | |
1238 } | |
1239 | |
1240 std::auto_ptr<OrthancStone::FloatTextureSceneLayer> texture; | |
1241 | |
1242 { | |
1243 // This is the case of a grayscale frame. Convert it to Float32. | |
1244 std::auto_ptr<Orthanc::Image> converted(new Orthanc::Image(Orthanc::PixelFormat_Float32, | |
1245 source.GetWidth(), | |
1246 source.GetHeight(), | |
1247 false)); | |
1248 Orthanc::ImageProcessing::Convert(*converted, source); | |
1249 | |
1250 // Correct rescale slope/intercept if need be | |
1251 data_.ApplyRescale(*converted, (sourceFormat == Orthanc::PixelFormat_Grayscale32)); | |
1252 | |
1253 texture.reset(new OrthancStone::FloatTextureSceneLayer(*converted)); | |
1254 } | |
1255 | |
1256 if (data_.hasDefaultWindowing_) | |
1257 { | |
1258 texture->SetCustomWindowing(data_.defaultWindowingCenter_, | |
1259 data_.defaultWindowingWidth_); | |
1260 } | |
1261 | |
1262 return texture.release(); | |
1263 } | |
1170 } | 1264 } |
1171 }; | 1265 }; |
1172 | 1266 |
1173 | 1267 |
1174 class DicomVolumeImage : public boost::noncopyable | 1268 class DicomVolumeImage : public boost::noncopyable |
1397 revision_ ++; | 1491 revision_ ++; |
1398 slicesRevision_[index] += 1; | 1492 slicesRevision_[index] += 1; |
1399 } | 1493 } |
1400 } | 1494 } |
1401 }; | 1495 }; |
1496 | |
1497 | |
1498 | |
1499 class IDicomVolumeSource : public boost::noncopyable | |
1500 { | |
1501 public: | |
1502 virtual ~IDicomVolumeSource() | |
1503 { | |
1504 } | |
1505 | |
1506 virtual const DicomVolumeImage& GetVolume() const = 0; | |
1507 | |
1508 virtual void NotifyAxialSliceAccessed(unsigned int sliceIndex) = 0; | |
1509 }; | |
1402 | 1510 |
1403 | 1511 |
1404 | 1512 |
1405 class VolumeSeriesOrthancLoader : public OrthancStone::IObserver | 1513 class VolumeSeriesOrthancLoader : |
1514 public OrthancStone::IObserver, | |
1515 public IDicomVolumeSource | |
1406 { | 1516 { |
1407 private: | 1517 private: |
1408 static const unsigned int LOW_QUALITY = 0; | 1518 static const unsigned int LOW_QUALITY = 0; |
1409 static const unsigned int MIDDLE_QUALITY = 1; | 1519 static const unsigned int MIDDLE_QUALITY = 1; |
1410 static const unsigned int BEST_QUALITY = 2; | 1520 static const unsigned int BEST_QUALITY = 2; |
1577 std::auto_ptr<Refactoring::OrthancRestApiCommand> command(new Refactoring::OrthancRestApiCommand); | 1687 std::auto_ptr<Refactoring::OrthancRestApiCommand> command(new Refactoring::OrthancRestApiCommand); |
1578 command->SetUri("/series/" + seriesId + "/instances-tags"); | 1688 command->SetUri("/series/" + seriesId + "/instances-tags"); |
1579 | 1689 |
1580 oracle_.Schedule(*this, command.release()); | 1690 oracle_.Schedule(*this, command.release()); |
1581 } | 1691 } |
1692 | |
1693 | |
1694 virtual const DicomVolumeImage& GetVolume() const | |
1695 { | |
1696 return volume_; | |
1697 } | |
1698 | |
1699 | |
1700 virtual void NotifyAxialSliceAccessed(unsigned int sliceIndex) | |
1701 { | |
1702 if (strategy_.get() == NULL) | |
1703 { | |
1704 // Should have called GetVolume().HasGeometry() before | |
1705 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
1706 } | |
1707 else | |
1708 { | |
1709 strategy_->SetCurrent(sliceIndex); | |
1710 } | |
1711 } | |
1582 }; | 1712 }; |
1583 | 1713 |
1584 | 1714 |
1585 | 1715 |
1586 #if 0 | 1716 #if 0 |
1659 } | 1789 } |
1660 };*/ | 1790 };*/ |
1661 | 1791 |
1662 | 1792 |
1663 | 1793 |
1664 class DicomVolumeSlicer : public IVolumeSlicer | 1794 class DicomVolumeMPRSlicer : public IVolumeSlicer |
1665 { | 1795 { |
1666 private: | 1796 private: |
1667 OrthancStone::Scene2D& scene_; | 1797 OrthancStone::Scene2D& scene_; |
1668 int layerDepth_; | 1798 int layerDepth_; |
1669 const DicomVolumeImage& volume_; | 1799 IDicomVolumeSource& source_; |
1670 bool first_; | 1800 bool first_; |
1671 OrthancStone::VolumeProjection lastProjection_; | 1801 OrthancStone::VolumeProjection lastProjection_; |
1672 unsigned int lastSliceIndex_; | 1802 unsigned int lastSliceIndex_; |
1673 uint64_t lastSliceRevision_; | 1803 uint64_t lastSliceRevision_; |
1674 | 1804 |
1675 public: | 1805 public: |
1676 DicomVolumeSlicer(OrthancStone::Scene2D& scene, | 1806 DicomVolumeMPRSlicer(OrthancStone::Scene2D& scene, |
1677 int layerDepth, | 1807 int layerDepth, |
1678 const DicomVolumeImage& volume) : | 1808 IDicomVolumeSource& source) : |
1679 scene_(scene), | 1809 scene_(scene), |
1680 layerDepth_(layerDepth), | 1810 layerDepth_(layerDepth), |
1681 volume_(volume), | 1811 source_(source), |
1682 first_(true) | 1812 first_(true) |
1683 { | 1813 { |
1684 } | 1814 } |
1685 | 1815 |
1686 virtual void SetViewportPlane(const OrthancStone::CoordinateSystem3D& plane) | 1816 virtual void SetViewportPlane(const OrthancStone::CoordinateSystem3D& plane) |
1687 { | 1817 { |
1688 if (!volume_.HasGeometry()) | 1818 if (!source_.GetVolume().HasGeometry() || |
1819 source_.GetVolume().GetSlicesCount() == 0) | |
1689 { | 1820 { |
1690 scene_.DeleteLayer(layerDepth_); | 1821 scene_.DeleteLayer(layerDepth_); |
1691 return; | 1822 return; |
1692 } | 1823 } |
1693 | 1824 |
1694 OrthancStone::VolumeProjection projection; | 1825 OrthancStone::VolumeProjection projection; |
1695 unsigned int sliceIndex; | 1826 unsigned int sliceIndex; |
1696 if (!volume_.GetImage().GetGeometry().DetectSlice(projection, sliceIndex, plane)) | 1827 if (!source_.GetVolume().GetImage().GetGeometry().DetectSlice(projection, sliceIndex, plane)) |
1697 { | 1828 { |
1698 // The cutting plane is neither axial, nor coronal, nor | 1829 // The cutting plane is neither axial, nor coronal, nor |
1699 // sagittal. Could use "VolumeReslicer" here. | 1830 // sagittal. Could use "VolumeReslicer" here. |
1700 scene_.DeleteLayer(layerDepth_); | 1831 scene_.DeleteLayer(layerDepth_); |
1701 return; | 1832 return; |
1702 } | 1833 } |
1703 | 1834 |
1704 uint64_t sliceRevision; | 1835 uint64_t sliceRevision; |
1705 if (projection == OrthancStone::VolumeProjection_Axial) | 1836 if (projection == OrthancStone::VolumeProjection_Axial) |
1706 { | 1837 { |
1707 sliceRevision = volume_.GetSliceRevision(sliceIndex); | 1838 sliceRevision = source_.GetVolume().GetSliceRevision(sliceIndex); |
1839 | |
1840 if (first_ || | |
1841 lastSliceIndex_ != sliceIndex) | |
1842 { | |
1843 // Reorder the prefetching queue | |
1844 source_.NotifyAxialSliceAccessed(sliceIndex); | |
1845 } | |
1708 } | 1846 } |
1709 else | 1847 else |
1710 { | 1848 { |
1711 // For coronal and sagittal projections, we take the global | 1849 // For coronal and sagittal projections, we take the global |
1712 // revision of the volume | 1850 // revision of the volume |
1713 sliceRevision = volume_.GetRevision(); | 1851 sliceRevision = source_.GetVolume().GetRevision(); |
1714 } | 1852 } |
1715 | 1853 |
1716 if (first_ || | 1854 if (first_ || |
1717 lastProjection_ == projection || | 1855 lastProjection_ != projection || |
1718 lastSliceIndex_ == sliceIndex || | 1856 lastSliceIndex_ != sliceIndex || |
1719 lastSliceRevision_ == sliceRevision) | 1857 lastSliceRevision_ != sliceRevision) |
1720 { | 1858 { |
1721 // Eiter the viewport plane, or the content of the slice have not | 1859 // Either the viewport plane, or the content of the slice have not |
1722 // changed since the last time the layer was set: Update is needed | 1860 // changed since the last time the layer was set: Update is needed |
1723 | 1861 |
1724 first_ = false; | 1862 first_ = false; |
1725 lastProjection_ = projection; | 1863 lastProjection_ = projection; |
1726 lastSliceIndex_ = sliceIndex; | 1864 lastSliceIndex_ = sliceIndex; |
1727 lastSliceRevision_ = sliceRevision; | 1865 lastSliceRevision_ = sliceRevision; |
1866 | |
1867 std::auto_ptr<OrthancStone::TextureBaseSceneLayer> texture; | |
1728 | 1868 |
1729 { | 1869 { |
1730 OrthancStone::ImageBuffer3D::SliceReader reader(volume_.GetImage(), projection, sliceIndex); | 1870 const DicomInstanceParameters& parameters = source_.GetVolume().GetSliceParameters |
1731 | 1871 (projection == OrthancStone::VolumeProjection_Axial ? sliceIndex : 0); |
1732 // TODO: Convert the image to Float32 or RGB24 | 1872 |
1733 | 1873 OrthancStone::ImageBuffer3D::SliceReader reader(source_.GetVolume().GetImage(), projection, sliceIndex); |
1734 // TODO: Set the layer | 1874 texture.reset(parameters.CreateTexture(reader.GetAccessor())); |
1735 } | 1875 } |
1876 | |
1877 // TODO - | |
1878 // void SetOrigin(double x, double y); | |
1879 // void SetPixelSpacing(double sx, double sy); | |
1880 // void SetAngle(double angle); | |
1881 // void SetLinearInterpolation(bool isLinearInterpolation); | |
1882 | |
1883 | |
1884 scene_.SetLayer(layerDepth_, texture.release()); | |
1736 } | 1885 } |
1737 } | 1886 } |
1738 }; | 1887 }; |
1739 | 1888 |
1740 | 1889 |