Mercurial > hg > orthanc-stone
changeset 1964:2034ae383cfd deep-learning
integration default->deep-learning
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Fri, 28 Oct 2022 07:47:55 +0200 |
parents | 0661115af939 (current diff) 79fdc3b1f031 (diff) |
children | 963f28eb40cb |
files | Applications/StoneWebViewer/WebApplication/app.js Applications/StoneWebViewer/WebApplication/index.html Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp |
diffstat | 20 files changed, 567 insertions(+), 78 deletions(-) [+] |
line wrap: on
line diff
--- a/Applications/Samples/RtViewerPlugin/CMakeLists.txt Tue Aug 16 15:05:51 2022 +0200 +++ b/Applications/Samples/RtViewerPlugin/CMakeLists.txt Fri Oct 28 07:47:55 2022 +0200 @@ -28,7 +28,7 @@ set(ORTHANC_FRAMEWORK_DEFAULT_VERSION "mainline") set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "hg") else() - set(ORTHANC_FRAMEWORK_DEFAULT_VERSION "1.10.1") + set(ORTHANC_FRAMEWORK_DEFAULT_VERSION "1.11.2") set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "web") endif()
--- a/Applications/Samples/WebAssembly/CMakeLists.txt Tue Aug 16 15:05:51 2022 +0200 +++ b/Applications/Samples/WebAssembly/CMakeLists.txt Fri Oct 28 07:47:55 2022 +0200 @@ -29,7 +29,7 @@ set(ORTHANC_FRAMEWORK_DEFAULT_VERSION "mainline") set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "hg") else() - set(ORTHANC_FRAMEWORK_DEFAULT_VERSION "1.10.1") + set(ORTHANC_FRAMEWORK_DEFAULT_VERSION "1.11.2") set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "web") endif()
--- a/Applications/StoneWebViewer/NEWS Tue Aug 16 15:05:51 2022 +0200 +++ b/Applications/StoneWebViewer/NEWS Fri Oct 28 07:47:55 2022 +0200 @@ -1,8 +1,15 @@ Pending changes in the mainline =============================== +* Display of orientation markers +* New configuration options: + - "ShowInfoPanelAtStartup" to control the info panel at startup + - "ShowUserPreferencesButton" to show the button for setting preferences + - "ShowNotForDiagnosticUsageDisclaimer" to show disclaimer about diagnostic usage + - "DicomWebHttpHeaders" to set HTTP headers in DICOMweb requests * More tolerance wrt. bad values of the Pixel Spacing (0028,0030) tag * Support of DICOM images without the Study Date (0008,0020) tag +* Fix handling of "token": The authorization header was not set in QIDO-RS requests Version 2.3 (2022-03-24)
--- a/Applications/StoneWebViewer/NOTES.txt Tue Aug 16 15:05:51 2022 +0200 +++ b/Applications/StoneWebViewer/NOTES.txt Fri Oct 28 07:47:55 2022 +0200 @@ -92,6 +92,22 @@ displayed at the startup. +Minor changes +------------- + +- Option "ShowInfoPanelButtonEnabled" in the Osimis Web viewer is + named "ShowUserPreferencesButton" in the Stone Web viewer. + +- Option "AlwaysShowNotForDiagnosticUsageDisclaimer" in the Osimis Web + viewer is named "ShowNotForDiagnosticUsageDisclaimer" in the Stone + Web viewer. + +- The allowed values for option "ShowInfoPanelAtStartup" are "Always", + "Never" of "User" (note the first character in upper case). In the + Osimis Web viewer, these options were in lower case. + + + Authorization to the DICOMweb server (new in 2.0) ====================================
--- a/Applications/StoneWebViewer/Plugin/Plugin.cpp Tue Aug 16 15:05:51 2022 +0200 +++ b/Applications/StoneWebViewer/Plugin/Plugin.cpp Fri Oct 28 07:47:55 2022 +0200 @@ -65,7 +65,8 @@ { std::vector<std::string> tokens; Orthanc::Toolbox::TokenizeString(tokens, version, '.'); - if (tokens.size() != 2) + if (tokens.size() != 2 && + tokens.size() != 3) { throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, "Bad version of the DICOMweb plugin: " + version);
--- a/Applications/StoneWebViewer/Version.cmake Tue Aug 16 15:05:51 2022 +0200 +++ b/Applications/StoneWebViewer/Version.cmake Fri Oct 28 07:47:55 2022 +0200 @@ -24,6 +24,6 @@ set(ORTHANC_FRAMEWORK_DEFAULT_VERSION "mainline") set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "hg") else() - set(ORTHANC_FRAMEWORK_DEFAULT_VERSION "1.10.1") + set(ORTHANC_FRAMEWORK_DEFAULT_VERSION "1.11.2") set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "web") endif()
--- a/Applications/StoneWebViewer/WebApplication/app.js Tue Aug 16 15:05:51 2022 +0200 +++ b/Applications/StoneWebViewer/WebApplication/app.js Fri Oct 28 07:47:55 2022 +0200 @@ -1123,8 +1123,6 @@ if (localStorage.settingSoftwareRendering) { this.settingSoftwareRendering = (localStorage.settingSoftwareRendering == '1'); } - - this.modalNotDiagnostic = this.settingNotDiagnostic; var that = this; @@ -1157,6 +1155,11 @@ window.addEventListener('StoneInitialized', function() { + /** + * Do NOT modify the order of the calls to "stone.XXX()" in this + * section, otherwise the HTTP headers might not be properly set. + **/ + stone.Setup(Module); stone.SetDicomWebRoot(app.globalConfiguration.DicomWebRoot, true /* assume "/rendered" is available in DICOMweb (could be a configuration option) */); @@ -1166,17 +1169,37 @@ stone.SetDicomCacheSize(app.globalConfiguration.DicomCacheSize); } - if ('SkipSeriesFromModalities' in app.globalConfiguration) { - stone.SetSkipSeriesFromModalities(JSON.stringify(app.globalConfiguration.SkipSeriesFromModalities)); + // Calls to "stone.AddHttpHeader()" must be after "stone.SetDicomWebRoot()", + // and before "stone.SetSkipSeriesFromModalities()" + for (var header in app.globalConfiguration.DicomWebHttpHeaders) { + stone.AddHttpHeader(header, app.globalConfiguration.DicomWebHttpHeaders[header]); } // Bearer token is new in Stone Web viewer 2.0 var token = getParameterFromUrl('token'); - if (token !== undefined) - { + if (token !== undefined) { stone.AddHttpHeader('Authorization', 'Bearer ' + token); } + + + /** + * Calls to "stone.XXX()" can be reordered after this point. + **/ + if ('SkipSeriesFromModalities' in app.globalConfiguration) { + stone.SetSkipSeriesFromModalities(JSON.stringify(app.globalConfiguration.SkipSeriesFromModalities)); + } + + if (app.globalConfiguration.ShowInfoPanelAtStartup == 'Always') { + app.modalNotDiagnostic = true; + } else if (app.globalConfiguration.ShowInfoPanelAtStartup == 'Never') { + app.modalNotDiagnostic = false; + } else if (app.globalConfiguration.ShowInfoPanelAtStartup == 'User') { + app.modalNotDiagnostic = app.settingNotDiagnostic; + } else { + alert('Bad value for option "ShowInfoPanelAtStartup": ' + app.globalConfiguration.ShowInfoPanelAtStartup); + } + console.warn('Stone properly initialized'); app.stoneWebViewerVersion = stone.GetStoneWebViewerVersion();
--- a/Applications/StoneWebViewer/WebApplication/configuration.json Tue Aug 16 15:05:51 2022 +0200 +++ b/Applications/StoneWebViewer/WebApplication/configuration.json Fri Oct 28 07:47:55 2022 +0200 @@ -119,6 +119,35 @@ /** * Define a list of modality type that the viewer will ignore. **/ - "SkipSeriesFromModalities": ["SR", "SEG", "PR"] + "SkipSeriesFromModalities": ["SR", "SEG", "PR"], + + /** + * Whether to display the info panel at startup. Allowed values: + * "Always", "Never", "User". With "User", the user can decide to + * show or not the info panel in the user preferences panel (this + * is implemented using a cookie). (New in Stone Web viewer 2.4) + **/ + "ShowInfoPanelAtStartup": "User", + + /** + * Whether to give access to the user preferences window. (New in + * Stone Web viewer 2.4) + **/ + "ShowUserPreferencesButton" : true, + + /** + * Display a "not for diagnostic usage" disclaimer above the list + * of studies/series. (New in Stone Web viewer 2.4) + **/ + "ShowNotForDiagnosticUsageDisclaimer": true, + + /** + * HTTP headers to be set in each request to the DICOMweb server. + * Note that the value of the headers can be taken from the + * environment variables. + **/ + "DicomWebHttpHeaders" : { + /* "Authorization" : "Bearer ${USER}" */ + } } }
--- a/Applications/StoneWebViewer/WebApplication/index.html Tue Aug 16 15:05:51 2022 +0200 +++ b/Applications/StoneWebViewer/WebApplication/index.html Fri Oct 28 07:47:55 2022 +0200 @@ -40,11 +40,14 @@ </p> </div> <div class="wvInfoPopupForm"> + <div v-if="globalConfiguration.ShowInfoPanelAtStartup == 'User'"> + <br> + <label>Show this information at startup + <input type="checkbox" style="margin-left: 20px" v-model="settingNotDiagnostic"> + </label> + <br> + </div> <br> - <label>Show this information at startup - <input type="checkbox" style="margin-left: 20px" v-model="settingNotDiagnostic"> - </label> - <br><br> <div style="text-align: center;"> <button class="wvInfoPopupCloseButton" @click="modalNotDiagnostic = false"> Close @@ -76,10 +79,12 @@ <h3>User preferences</h3> </div> <div class="wvInfoPopupForm"> - <label>Warn about the intended use at startup - <input type="checkbox" style="margin-left: 20px" v-model="settingNotDiagnostic"> - </label> - <br> + <div v-if="globalConfiguration.ShowInfoPanelAtStartup == 'User'"> + <label>Warn about the intended use at startup + <input type="checkbox" style="margin-left: 20px" v-model="settingNotDiagnostic"> + </label> + <br> + </div> <label>Use software rendering (will reload the viewer) <input type="checkbox" style="margin-left: 20px" v-model="settingSoftwareRendering"> </label> @@ -172,7 +177,9 @@ <i class="fa fa-th"></i> </div> - <!--p class="clear disclaimer mbn">For patients, teachers and researchers.</p--> + <p v-if="globalConfiguration.ShowNotForDiagnosticUsageDisclaimer" class="clear disclaimer mbn"> + For patients, researchers and quality assurance. Not for diagnostic usage. + </p> </div> <div class="wvLayoutLeft__contentMiddle"> @@ -584,7 +591,7 @@ </button> </div> - <div class="ng-scope inline-object"> + <div class="ng-scope inline-object" v-if="globalConfiguration.ShowUserPreferencesButton"> <button class="wvButton--underline text-center" data-toggle="tooltip" data-title="User preferences" v-on:click="modalPreferences = true">
--- a/Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp Tue Aug 16 15:05:51 2022 +0200 +++ b/Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp Fri Oct 28 07:47:55 2022 +0200 @@ -1585,11 +1585,12 @@ private: static const int LAYER_TEXTURE = 0; - static const int LAYER_DEEP_LEARNING = 1; - static const int LAYER_OVERLAY = 2; + static const int LAYER_OVERLAY = 1; + static const int LAYER_ORIENTATION_MARKERS = 2; static const int LAYER_REFERENCE_LINES = 3; static const int LAYER_ANNOTATIONS_OSIRIX = 4; static const int LAYER_ANNOTATIONS_STONE = 5; + static const int LAYER_DEEP_LEARNING = 6; class ICommand : public Orthanc::IDynamicObject @@ -2257,6 +2258,46 @@ StoneAnnotationsRegistry::GetInstance().Load(*stoneAnnotations_, instance.GetSopInstanceUid(), frameIndex); + // Orientation markers, new in Stone Web viewer 2.4 + std::unique_ptr<OrthancStone::MacroSceneLayer> orientationMarkers; + + if (instance.GetGeometry().IsValid()) + { + orientationMarkers.reset(new OrthancStone::MacroSceneLayer); + + std::string top, bottom, left, right; + instance.GetGeometry().GetOrientationMarkers(top, bottom, left, right); + + std::unique_ptr<OrthancStone::TextSceneLayer> text; + + text.reset(new OrthancStone::TextSceneLayer); + text->SetText(top); + text->SetPosition(pixelSpacingX * static_cast<double>(frame.GetWidth()) / 2.0, 0); + text->SetAnchor(OrthancStone::BitmapAnchor_TopCenter); + orientationMarkers->AddLayer(text.release()); + + text.reset(new OrthancStone::TextSceneLayer); + text->SetText(bottom); + text->SetPosition(pixelSpacingX * static_cast<double>(frame.GetWidth()) / 2.0, + pixelSpacingY * static_cast<double>(frame.GetHeight())); + text->SetAnchor(OrthancStone::BitmapAnchor_BottomCenter); + orientationMarkers->AddLayer(text.release()); + + text.reset(new OrthancStone::TextSceneLayer); + text->SetText(left); + text->SetPosition(0, pixelSpacingY * static_cast<double>(frame.GetHeight()) / 2.0); + text->SetAnchor(OrthancStone::BitmapAnchor_CenterLeft); + orientationMarkers->AddLayer(text.release()); + + text.reset(new OrthancStone::TextSceneLayer); + text->SetText(right); + text->SetPosition(pixelSpacingX * static_cast<double>(frame.GetWidth()), + pixelSpacingY * static_cast<double>(frame.GetHeight()) / 2.0); + text->SetAnchor(OrthancStone::BitmapAnchor_CenterRight); + orientationMarkers->AddLayer(text.release()); + } + + { std::unique_ptr<OrthancStone::IViewport::ILock> lock(viewport_->Lock()); @@ -2282,6 +2323,15 @@ scene.DeleteLayer(LAYER_ANNOTATIONS_OSIRIX); } + if (orientationMarkers.get() != NULL) + { + scene.SetLayer(LAYER_ORIENTATION_MARKERS, orientationMarkers.release()); + } + else + { + scene.DeleteLayer(LAYER_ORIENTATION_MARKERS); + } + if (deepLearningLayer.get() != NULL) { scene.SetLayer(LAYER_DEEP_LEARNING, deepLearningLayer.release());
--- a/OrthancStone/Resources/Orthanc/CMake/DownloadOrthancFramework.cmake Tue Aug 16 15:05:51 2022 +0200 +++ b/OrthancStone/Resources/Orthanc/CMake/DownloadOrthancFramework.cmake Fri Oct 28 07:47:55 2022 +0200 @@ -146,6 +146,12 @@ set(ORTHANC_FRAMEWORK_MD5 "8610c82d9153f22e929f2110f8f60279") elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.10.1") set(ORTHANC_FRAMEWORK_MD5 "caf667fc5ea452b3d0c2f70bfd02599c") + elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.11.0") + set(ORTHANC_FRAMEWORK_MD5 "962c4a4a706a2ef28b390d8515dd7091") + elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.11.1") + set(ORTHANC_FRAMEWORK_MD5 "a39661c406adf22cf574fde290cf4bbf") + elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.11.2") + set(ORTHANC_FRAMEWORK_MD5 "ede3de356493a8868545f8cb4b8bc8b5") # Below this point are development snapshots that were used to # release some plugin, before an official release of the Orthanc
--- a/OrthancStone/Resources/Orthanc/Toolchains/LinuxStandardBaseToolchain.cmake Tue Aug 16 15:05:51 2022 +0200 +++ b/OrthancStone/Resources/Orthanc/Toolchains/LinuxStandardBaseToolchain.cmake Fri Oct 28 07:47:55 2022 +0200 @@ -22,11 +22,11 @@ # # Full build, as used on the BuildBot CIS: # -# $ LSB_CC=gcc-4.8 LSB_CXX=g++-4.8 cmake ../OrthancServer/ -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE=../OrthancFramework/Resources/Toolchains/LinuxStandardBaseToolchain.cmake -DUSE_LEGACY_JSONCPP=ON -DUSE_LEGACY_LIBICU=ON -DBOOST_LOCALE_BACKEND=icu -DENABLE_PKCS11=ON -G Ninja +# $ LSB_CC=gcc-4.8 LSB_CXX=g++-4.8 cmake ../OrthancServer/ -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE=../OrthancFramework/Resources/Toolchains/LinuxStandardBaseToolchain.cmake -DUSE_LEGACY_JSONCPP=ON -DUSE_LEGACY_LIBICU=ON -DUSE_LEGACY_BOOST=ON -DBOOST_LOCALE_BACKEND=icu -DENABLE_PKCS11=ON -G Ninja # # Or, more lightweight version (without libp11 and ICU): # -# $ LSB_CC=gcc-4.8 LSB_CXX=g++-4.8 cmake ../OrthancServer/ -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE=../OrthancFramework/Resources/Toolchains/LinuxStandardBaseToolchain.cmake -DUSE_LEGACY_JSONCPP=ON -G Ninja +# $ LSB_CC=gcc-4.8 LSB_CXX=g++-4.8 cmake ../OrthancServer/ -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE=../OrthancFramework/Resources/Toolchains/LinuxStandardBaseToolchain.cmake -DUSE_LEGACY_JSONCPP=ON -DUSE_LEGACY_BOOST=ON -G Ninja # INCLUDE(CMakeForceCompiler)
--- a/OrthancStone/Sources/Toolbox/BucketAccumulator2D.cpp Tue Aug 16 15:05:51 2022 +0200 +++ b/OrthancStone/Sources/Toolbox/BucketAccumulator2D.cpp Fri Oct 28 07:47:55 2022 +0200 @@ -195,7 +195,7 @@ for (size_t x = 0; x < mapperX_.GetSize(); x++) { - fprintf(fp, "%7ld ", GetBucketContentSize(x, y)); + fprintf(fp, "%7ld ", static_cast<long>(GetBucketContentSize(x, y))); } fprintf(fp, "\n");
--- a/OrthancStone/Sources/Toolbox/CoordinateSystem3D.cpp Tue Aug 16 15:05:51 2022 +0200 +++ b/OrthancStone/Sources/Toolbox/CoordinateSystem3D.cpp Fri Oct 28 07:47:55 2022 +0200 @@ -381,4 +381,109 @@ return CoordinateSystem3D(a, axisX, axisY); } + + + static std::string GetOrientationString(const Vector& v) + { + /** + * This function directly comes from David Clunie: + * https://sites.google.com/site/dicomnotes/ + * + * It is also a C++ reimplementation of function + * "getOrientationString()" from Cornerstone: + * https://bitbucket.org/osimis/osimis-webviewer-plugin/src/master/frontend/local_dependencies/cornerstoneTools/cornerstoneTools.js + **/ + + const char orientationX = v[0] < 0 ? 'R' : 'L'; + const char orientationY = v[1] < 0 ? 'A' : 'P'; + const char orientationZ = v[2] < 0 ? 'F' : 'H'; + + double absX = abs(v[0]); + double absY = abs(v[1]); + double absZ = abs(v[2]); + + std::string result; + + static const double THRESHOLD = 0.0001; + + for (unsigned int i = 0; i < 3; i++) + { + if (absX > THRESHOLD && absX > absY && absX > absZ) + { + result.push_back(orientationX); + absX = 0; + } + else if (absY > THRESHOLD && absY > absX && absY > absZ) + { + result.push_back(orientationY); + absY = 0; + } + else if (absZ > THRESHOLD && absZ > absX && absZ > absY) + { + result.push_back(orientationZ); + absZ = 0; + } + else + { + break; + } + } + + return result; + } + + + static std::string InvertOrientationString(const std::string& source) + { + std::string target; + target.resize(source.length()); + + for (unsigned int i = 0; i < source.length(); i++) + { + switch (source[i]) + { + case 'H': target[i] = 'F'; break; + case 'F': target[i] = 'H'; break; + case 'R': target[i] = 'L'; break; + case 'L': target[i] = 'R'; break; + case 'A': target[i] = 'P'; break; + case 'P': target[i] = 'A'; break; + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } + + return target; + } + + + void CoordinateSystem3D::GetOrientationMarkers(std::string& top /* out */, + std::string& bottom /* out */, + std::string& left /* out */, + std::string& right /* out */) const + { + if (IsValid()) + { + /** + * This is a C++ reimplementation of function + * "getOrientationMarkers()" from Cornerstone: + * https://bitbucket.org/osimis/osimis-webviewer-plugin/src/master/frontend/local_dependencies/cornerstoneTools/cornerstoneTools.js + * + * ImageOrientationPatient is row cosines then column cosines + * (i.e. horizontal then vertical). + * https://dicom.nema.org/medical/dicom/current/output/html/part03.html#sect_C.7.6.2 + **/ + const Vector& rowCosines = axisX_; // horizontal + const Vector& columnCosines = axisY_; // vertical + + bottom = GetOrientationString(columnCosines); + right = GetOrientationString(rowCosines); + top = InvertOrientationString(bottom); + left = InvertOrientationString(right); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } }
--- a/OrthancStone/Sources/Toolbox/CoordinateSystem3D.h Tue Aug 16 15:05:51 2022 +0200 +++ b/OrthancStone/Sources/Toolbox/CoordinateSystem3D.h Fri Oct 28 07:47:55 2022 +0200 @@ -160,5 +160,10 @@ static CoordinateSystem3D CreateFromThreePoints(const Vector& a, const Vector& b, const Vector& c); + + void GetOrientationMarkers(std::string& top /* out */, + std::string& bottom /* out */, + std::string& left /* out */, + std::string& right /* out */) const; }; }
--- a/OrthancStone/UnitTestsSources/GeometryToolboxTests.cpp Tue Aug 16 15:05:51 2022 +0200 +++ b/OrthancStone/UnitTestsSources/GeometryToolboxTests.cpp Fri Oct 28 07:47:55 2022 +0200 @@ -1091,3 +1091,86 @@ ASSERT_EQ(376u - 323u, slice); } } + + +TEST(GeometryToolbox, OrientationMarkers) +{ + std::string top, bottom, left, right; + + OrthancStone::CoordinateSystem3D s; + ASSERT_FALSE(s.IsValid()); + ASSERT_THROW(s.GetOrientationMarkers(top, bottom, left, right), Orthanc::OrthancException); + + { + // BRAINIX series: 3ca69615-fcd4a4fb-e5f2cc9d-9c7a49a5-add98bbf + s = OrthancStone::CoordinateSystem3D( + "0\\0\\0", + "0.99971222877502\\7.8810308973E-12\\0.02398800104856\\-0.0017278126906\\0.99740260839462\\0.07200747728347"); + ASSERT_TRUE(s.IsValid()); + s.GetOrientationMarkers(top, bottom, left, right); + ASSERT_EQ("AFL", top); + ASSERT_EQ("PHR", bottom); + ASSERT_EQ("RF", left); + ASSERT_EQ("LH", right); + } + + { + // BRAINIX series: dc0216d2-a406a5ad-31ef7a78-113ae9d9-29939f9e + s = OrthancStone::CoordinateSystem3D( + "0\\0\\0", + "0.99971222877502\\7.8810308973E-12\\0.02398800104856\\0.02392569556832\\0.07202820479869\\-0.9971156120300"); + s.GetOrientationMarkers(top, bottom, left, right); + ASSERT_EQ("HAR", top); + ASSERT_EQ("FPL", bottom); + ASSERT_EQ("RF", left); + ASSERT_EQ("LH", right); + } + + { + // ASSURANCETOURIX series: 1de00990-03680ef4-0be6bd5b-73a7d350-fb46abfa + s = OrthancStone::CoordinateSystem3D( + "0\\0\\0", + "1\\0\\0\\0\\1\\0"); + s.GetOrientationMarkers(top, bottom, left, right); + ASSERT_EQ("A", top); + ASSERT_EQ("P", bottom); + ASSERT_EQ("R", left); + ASSERT_EQ("L", right); + } + + { + // KNEE series: 4d04593b-953ced51-87e93f11-ae4cf03c-25defdcd + s = OrthancStone::CoordinateSystem3D( + "0\\0\\0", + "-0\\1\\0\\-0\\-0\\-1"); + s.GetOrientationMarkers(top, bottom, left, right); + ASSERT_EQ("H", top); + ASSERT_EQ("F", bottom); + ASSERT_EQ("A", left); + ASSERT_EQ("P", right); + } + + { + // KNEE series: 20b9d0c2-97d85e07-f4dbf4d2-f09e7e6a-0c19062e + s = OrthancStone::CoordinateSystem3D( + "0\\0\\0", + "0.999993\\-0.0036927\\0\\-0\\-0\\-1"); + s.GetOrientationMarkers(top, bottom, left, right); + ASSERT_EQ("H", top); + ASSERT_EQ("F", bottom); + ASSERT_EQ("RP", left); + ASSERT_EQ("LA", right); + } + + { + // KNEE series: f2635388-f01d497a-15f7c06b-ad7dba06-c4c599fe + s = OrthancStone::CoordinateSystem3D( + "0\\0\\0", + "0.999841\\0.000366209\\0.0178227\\-0.000427244\\0.999995\\0.00326546"); + s.GetOrientationMarkers(top, bottom, left, right); + ASSERT_EQ("AFL", top); + ASSERT_EQ("PHR", bottom); + ASSERT_EQ("RFA", left); + ASSERT_EQ("LHP", right); + } +}
--- a/RenderingPlugin/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp Tue Aug 16 15:05:51 2022 +0200 +++ b/RenderingPlugin/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp Fri Oct 28 07:47:55 2022 +0200 @@ -250,26 +250,52 @@ } } + // helper class to convert std::map of headers to the plugin SDK C structure + class PluginHttpHeaders + { + std::vector<const char*> headersKeys_; + std::vector<const char*> headersValues_; + public: + + PluginHttpHeaders(const std::map<std::string, std::string>& httpHeaders) + { + for (std::map<std::string, std::string>::const_iterator + it = httpHeaders.begin(); it != httpHeaders.end(); it++) + { + headersKeys_.push_back(it->first.c_str()); + headersValues_.push_back(it->second.c_str()); + } + } + + const char* const* GetKeys() + { + return (headersKeys_.empty() ? NULL : &headersKeys_[0]); + } + + const char* const* GetValues() + { + return (headersValues_.empty() ? NULL : &headersValues_[0]); + } + + uint32_t GetSize() + { + return static_cast<uint32_t>(headersKeys_.size()); + } + }; + bool MemoryBuffer::RestApiGet(const std::string& uri, const std::map<std::string, std::string>& httpHeaders, bool applyPlugins) { Clear(); - std::vector<const char*> headersKeys; - std::vector<const char*> headersValues; - - for (std::map<std::string, std::string>::const_iterator - it = httpHeaders.begin(); it != httpHeaders.end(); it++) - { - headersKeys.push_back(it->first.c_str()); - headersValues.push_back(it->second.c_str()); - } + PluginHttpHeaders headers(httpHeaders); return CheckHttp(OrthancPluginRestApiGet2( - GetGlobalContext(), &buffer_, uri.c_str(), httpHeaders.size(), - (headersKeys.empty() ? NULL : &headersKeys[0]), - (headersValues.empty() ? NULL : &headersValues[0]), applyPlugins)); + GetGlobalContext(), &buffer_, uri.c_str(), + headers.GetSize(), + headers.GetKeys(), + headers.GetValues(), applyPlugins)); } bool MemoryBuffer::RestApiPost(const std::string& uri, @@ -292,6 +318,41 @@ } } +#if HAS_ORTHANC_PLUGIN_GENERIC_CALL_REST_API == 1 + + bool MemoryBuffer::RestApiPost(const std::string& uri, + const void* body, + size_t bodySize, + const std::map<std::string, std::string>& httpHeaders, + bool applyPlugins) + { + MemoryBuffer answerHeaders; + uint16_t httpStatus; + + PluginHttpHeaders headers(httpHeaders); + + return CheckHttp(OrthancPluginCallRestApi(GetGlobalContext(), + &buffer_, + *answerHeaders, + &httpStatus, + OrthancPluginHttpMethod_Post, + uri.c_str(), + headers.GetSize(), headers.GetKeys(), headers.GetValues(), + body, bodySize, + applyPlugins)); + } + + + bool MemoryBuffer::RestApiPost(const std::string& uri, + const Json::Value& body, + const std::map<std::string, std::string>& httpHeaders, + bool applyPlugins) + { + std::string s; + WriteFastJson(s, body); + return RestApiPost(uri, s.c_str(), s.size(), httpHeaders, applyPlugins); + } +#endif bool MemoryBuffer::RestApiPut(const std::string& uri, const void* body, @@ -1457,6 +1518,30 @@ } } +#if HAS_ORTHANC_PLUGIN_GENERIC_CALL_REST_API == 1 + bool RestApiPost(Json::Value& result, + const std::string& uri, + const Json::Value& body, + const std::map<std::string, std::string>& httpHeaders, + bool applyPlugins) + { + MemoryBuffer answer; + + if (!answer.RestApiPost(uri, body, httpHeaders, applyPlugins)) + { + return false; + } + else + { + if (!answer.IsEmpty()) + { + answer.ToJson(result); + } + return true; + } + } +#endif + bool RestApiPost(Json::Value& result, const std::string& uri, @@ -1800,7 +1885,8 @@ bool OrthancPeers::DoGet(MemoryBuffer& target, size_t index, - const std::string& uri) const + const std::string& uri, + const std::map<std::string, std::string>& headers) const { if (index >= index_.size()) { @@ -1809,10 +1895,12 @@ OrthancPlugins::MemoryBuffer answer; uint16_t status; + PluginHttpHeaders pluginHeaders(headers); + OrthancPluginErrorCode code = OrthancPluginCallPeerApi (GetGlobalContext(), *answer, NULL, &status, peers_, static_cast<uint32_t>(index), OrthancPluginHttpMethod_Get, uri.c_str(), - 0, NULL, NULL, NULL, 0, timeout_); + pluginHeaders.GetSize(), pluginHeaders.GetKeys(), pluginHeaders.GetValues(), NULL, 0, timeout_); if (code == OrthancPluginErrorCode_Success) { @@ -1828,21 +1916,23 @@ bool OrthancPeers::DoGet(MemoryBuffer& target, const std::string& name, - const std::string& uri) const + const std::string& uri, + const std::map<std::string, std::string>& headers) const { size_t index; return (LookupName(index, name) && - DoGet(target, index, uri)); + DoGet(target, index, uri, headers)); } bool OrthancPeers::DoGet(Json::Value& target, size_t index, - const std::string& uri) const + const std::string& uri, + const std::map<std::string, std::string>& headers) const { MemoryBuffer buffer; - if (DoGet(buffer, index, uri)) + if (DoGet(buffer, index, uri, headers)) { buffer.ToJson(target); return true; @@ -1856,11 +1946,12 @@ bool OrthancPeers::DoGet(Json::Value& target, const std::string& name, - const std::string& uri) const + const std::string& uri, + const std::map<std::string, std::string>& headers) const { MemoryBuffer buffer; - if (DoGet(buffer, name, uri)) + if (DoGet(buffer, name, uri, headers)) { buffer.ToJson(target); return true; @@ -1875,22 +1966,24 @@ bool OrthancPeers::DoPost(MemoryBuffer& target, const std::string& name, const std::string& uri, - const std::string& body) const + const std::string& body, + const std::map<std::string, std::string>& headers) const { size_t index; return (LookupName(index, name) && - DoPost(target, index, uri, body)); + DoPost(target, index, uri, body, headers)); } bool OrthancPeers::DoPost(Json::Value& target, size_t index, const std::string& uri, - const std::string& body) const + const std::string& body, + const std::map<std::string, std::string>& headers) const { MemoryBuffer buffer; - if (DoPost(buffer, index, uri, body)) + if (DoPost(buffer, index, uri, body, headers)) { buffer.ToJson(target); return true; @@ -1905,11 +1998,12 @@ bool OrthancPeers::DoPost(Json::Value& target, const std::string& name, const std::string& uri, - const std::string& body) const + const std::string& body, + const std::map<std::string, std::string>& headers) const { MemoryBuffer buffer; - if (DoPost(buffer, name, uri, body)) + if (DoPost(buffer, name, uri, body, headers)) { buffer.ToJson(target); return true; @@ -1924,7 +2018,8 @@ bool OrthancPeers::DoPost(MemoryBuffer& target, size_t index, const std::string& uri, - const std::string& body) const + const std::string& body, + const std::map<std::string, std::string>& headers) const { if (index >= index_.size()) { @@ -1939,10 +2034,12 @@ OrthancPlugins::MemoryBuffer answer; uint16_t status; + PluginHttpHeaders pluginHeaders(headers); + OrthancPluginErrorCode code = OrthancPluginCallPeerApi (GetGlobalContext(), *answer, NULL, &status, peers_, static_cast<uint32_t>(index), OrthancPluginHttpMethod_Post, uri.c_str(), - 0, NULL, NULL, body.empty() ? NULL : body.c_str(), body.size(), timeout_); + pluginHeaders.GetSize(), pluginHeaders.GetKeys(), pluginHeaders.GetValues(), body.empty() ? NULL : body.c_str(), body.size(), timeout_); if (code == OrthancPluginErrorCode_Success) { @@ -1958,7 +2055,8 @@ bool OrthancPeers::DoPut(size_t index, const std::string& uri, - const std::string& body) const + const std::string& body, + const std::map<std::string, std::string>& headers) const { if (index >= index_.size()) { @@ -1973,10 +2071,12 @@ OrthancPlugins::MemoryBuffer answer; uint16_t status; + PluginHttpHeaders pluginHeaders(headers); + OrthancPluginErrorCode code = OrthancPluginCallPeerApi (GetGlobalContext(), *answer, NULL, &status, peers_, static_cast<uint32_t>(index), OrthancPluginHttpMethod_Put, uri.c_str(), - 0, NULL, NULL, body.empty() ? NULL : body.c_str(), body.size(), timeout_); + pluginHeaders.GetSize(), pluginHeaders.GetKeys(), pluginHeaders.GetValues(), body.empty() ? NULL : body.c_str(), body.size(), timeout_); if (code == OrthancPluginErrorCode_Success) { @@ -1991,16 +2091,18 @@ bool OrthancPeers::DoPut(const std::string& name, const std::string& uri, - const std::string& body) const + const std::string& body, + const std::map<std::string, std::string>& headers) const { size_t index; return (LookupName(index, name) && - DoPut(index, uri, body)); + DoPut(index, uri, body, headers)); } bool OrthancPeers::DoDelete(size_t index, - const std::string& uri) const + const std::string& uri, + const std::map<std::string, std::string>& headers) const { if (index >= index_.size()) { @@ -2009,10 +2111,12 @@ OrthancPlugins::MemoryBuffer answer; uint16_t status; + PluginHttpHeaders pluginHeaders(headers); + OrthancPluginErrorCode code = OrthancPluginCallPeerApi (GetGlobalContext(), *answer, NULL, &status, peers_, static_cast<uint32_t>(index), OrthancPluginHttpMethod_Delete, uri.c_str(), - 0, NULL, NULL, NULL, 0, timeout_); + pluginHeaders.GetSize(), pluginHeaders.GetKeys(), pluginHeaders.GetValues(), NULL, 0, timeout_); if (code == OrthancPluginErrorCode_Success) { @@ -2026,11 +2130,12 @@ bool OrthancPeers::DoDelete(const std::string& name, - const std::string& uri) const + const std::string& uri, + const std::map<std::string, std::string>& headers) const { size_t index; return (LookupName(index, name) && - DoDelete(index, uri)); + DoDelete(index, uri, headers)); } #endif @@ -3796,4 +3901,14 @@ } } #endif + + void GetHttpHeaders(std::map<std::string, std::string>& result, const OrthancPluginHttpRequest* request) + { + result.clear(); + + for (uint32_t i = 0; i < request->headersCount; ++i) + { + result[request->headersKeys[i]] = request->headersValues[i]; + } + } }
--- a/RenderingPlugin/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h Tue Aug 16 15:05:51 2022 +0200 +++ b/RenderingPlugin/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h Fri Oct 28 07:47:55 2022 +0200 @@ -115,6 +115,12 @@ # define HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP 0 #endif +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 9, 2) +# define HAS_ORTHANC_PLUGIN_GENERIC_CALL_REST_API 1 +#else +# define HAS_ORTHANC_PLUGIN_GENERIC_CALL_REST_API 0 +#endif + #if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 10, 1) # define HAS_ORTHANC_PLUGIN_WEBDAV 1 #else @@ -224,6 +230,19 @@ const Json::Value& body, bool applyPlugins); +#if HAS_ORTHANC_PLUGIN_GENERIC_CALL_REST_API == 1 + bool RestApiPost(const std::string& uri, + const Json::Value& body, + const std::map<std::string, std::string>& httpHeaders, + bool applyPlugins); + + bool RestApiPost(const std::string& uri, + const void* body, + size_t bodySize, + const std::map<std::string, std::string>& httpHeaders, + bool applyPlugins); +#endif + bool RestApiPut(const std::string& uri, const Json::Value& body, bool applyPlugins); @@ -535,6 +554,14 @@ size_t bodySize, bool applyPlugins); +#if HAS_ORTHANC_PLUGIN_GENERIC_CALL_REST_API == 1 + bool RestApiPost(Json::Value& result, + const std::string& uri, + const Json::Value& body, + const std::map<std::string, std::string>& httpHeaders, + bool applyPlugins); +#endif + bool RestApiPost(Json::Value& result, const std::string& uri, const Json::Value& body, @@ -728,53 +755,65 @@ bool DoGet(MemoryBuffer& target, size_t index, - const std::string& uri) const; + const std::string& uri, + const std::map<std::string, std::string>& headers) const; bool DoGet(MemoryBuffer& target, const std::string& name, - const std::string& uri) const; + const std::string& uri, + const std::map<std::string, std::string>& headers) const; bool DoGet(Json::Value& target, size_t index, - const std::string& uri) const; + const std::string& uri, + const std::map<std::string, std::string>& headers) const; bool DoGet(Json::Value& target, const std::string& name, - const std::string& uri) const; + const std::string& uri, + const std::map<std::string, std::string>& headers) const; bool DoPost(MemoryBuffer& target, size_t index, const std::string& uri, - const std::string& body) const; + const std::string& body, + const std::map<std::string, std::string>& headers) const; bool DoPost(MemoryBuffer& target, const std::string& name, const std::string& uri, - const std::string& body) const; + const std::string& body, + const std::map<std::string, std::string>& headers) const; bool DoPost(Json::Value& target, size_t index, const std::string& uri, - const std::string& body) const; + const std::string& body, + const std::map<std::string, std::string>& headers) const; bool DoPost(Json::Value& target, const std::string& name, const std::string& uri, - const std::string& body) const; + const std::string& body, + const std::map<std::string, std::string>& headers) const; bool DoPut(size_t index, const std::string& uri, - const std::string& body) const; + const std::string& body, + const std::map<std::string, std::string>& headers) const; bool DoPut(const std::string& name, const std::string& uri, - const std::string& body) const; + const std::string& body, + const std::map<std::string, std::string>& headers) const; bool DoDelete(size_t index, - const std::string& uri) const; + const std::string& uri, + const std::map<std::string, std::string>& headers) const; bool DoDelete(const std::string& name, - const std::string& uri) const; + const std::string& uri, + const std::map<std::string, std::string>& headers) const; }; #endif @@ -1264,7 +1303,8 @@ #endif }; - +// helper method to convert Http headers from the plugin SDK to a std::map +void GetHttpHeaders(std::map<std::string, std::string>& result, const OrthancPluginHttpRequest* request); #if HAS_ORTHANC_PLUGIN_WEBDAV == 1 class IWebDavCollection : public boost::noncopyable
--- a/RenderingPlugin/Sources/Plugin.cpp Tue Aug 16 15:05:51 2022 +0200 +++ b/RenderingPlugin/Sources/Plugin.cpp Fri Oct 28 07:47:55 2022 +0200 @@ -53,7 +53,7 @@ std::unique_ptr<OrthancStone::DicomStructureSet> rtstruct_; public: - Item(OrthancStone::DicomStructureSet* rtstruct) : + explicit Item(OrthancStone::DicomStructureSet* rtstruct) : rtstruct_(rtstruct) { if (rtstruct == NULL) @@ -62,7 +62,7 @@ } } - virtual size_t GetMemoryUsage() const + virtual size_t GetMemoryUsage() const ORTHANC_OVERRIDE { return 1; }
--- a/TODO Tue Aug 16 15:05:51 2022 +0200 +++ b/TODO Fri Oct 28 07:47:55 2022 +0200 @@ -38,7 +38,7 @@ * add a button (and/or a keyboard shortcut) to select the next series in the main viewport https://groups.google.com/g/orthanc-users/c/u_lH9aqKsdw/m/KQ7U9CkiAAAJ. In Osimis viewer, - this was implemented by up/down arrow keys (prev/next series). + this was implemented by up/down arrow keys (prev/next series) ------------ Known issues @@ -55,6 +55,8 @@ * the authorization header ('token' query arg) is not included in the HTTP headers. Tested with https://bitbucket.org/osimis/orthanc-setup-samples/src/master/docker/authorization-plugin-viewer-query-args/. +* the plugin does not work with DicomWeb plugin version 1.10.1 -> only 2 numbers are allowed: + https://groups.google.com/g/orthanc-users/c/xMcicKAldpM/m/b5Gz3wnyAQAJ ----------------- Code refactorings