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