# HG changeset patch # User Sebastien Jodogne # Date 1495122701 -7200 # Node ID acb60cbb83018b25a256e9e137b7f6f2a4a3fa57 # Parent 298f375dcb683ac79cffcea42bf8047f497e0a55 refactoring SeriesLoader diff -r 298f375dcb68 -r acb60cbb8301 Framework/Toolbox/MessagingToolbox.cpp --- a/Framework/Toolbox/MessagingToolbox.cpp Wed May 17 22:03:09 2017 +0200 +++ b/Framework/Toolbox/MessagingToolbox.cpp Thu May 18 17:51:41 2017 +0200 @@ -152,8 +152,19 @@ } - static void ParseJson(Json::Value& target, - const std::string& source) + bool ParseJson(Json::Value& target, + const void* content, + size_t size) + { + Json::Reader reader; + return reader.parse(reinterpret_cast(content), + reinterpret_cast(content) + size, + target); + } + + + static void ParseJsonException(Json::Value& target, + const std::string& source) { Json::Reader reader; if (!reader.parse(source, target)) @@ -169,7 +180,7 @@ { std::string tmp; orthanc.RestApiGet(tmp, uri); - ParseJson(target, tmp); + ParseJsonException(target, tmp); } @@ -180,7 +191,7 @@ { std::string tmp; orthanc.RestApiPost(tmp, uri, body); - ParseJson(target, tmp); + ParseJsonException(target, tmp); } diff -r 298f375dcb68 -r acb60cbb8301 Framework/Toolbox/MessagingToolbox.h --- a/Framework/Toolbox/MessagingToolbox.h Wed May 17 22:03:09 2017 +0200 +++ b/Framework/Toolbox/MessagingToolbox.h Thu May 18 17:51:41 2017 +0200 @@ -58,6 +58,10 @@ }; + bool ParseJson(Json::Value& target, + const void* content, + size_t size); + void RestApiGet(Json::Value& target, OrthancPlugins::IOrthancConnection& orthanc, const std::string& uri); diff -r 298f375dcb68 -r acb60cbb8301 Framework/Toolbox/OrthancSeriesLoader.cpp --- a/Framework/Toolbox/OrthancSeriesLoader.cpp Wed May 17 22:03:09 2017 +0200 +++ b/Framework/Toolbox/OrthancSeriesLoader.cpp Thu May 18 17:51:41 2017 +0200 @@ -327,7 +327,7 @@ slices_(new SetOfSlices) { /** - * The function "LoadSeriesFast()" might now behave properly if + * The function "LoadSeriesFast()" might not behave properly if * some slice has some outsider value for its normal, which * happens sometimes on reprojected series (e.g. coronal and * sagittal of Delphine). Don't use it. diff -r 298f375dcb68 -r acb60cbb8301 Resources/Orthanc/Plugins/Samples/Common/FullOrthancDataset.cpp --- a/Resources/Orthanc/Plugins/Samples/Common/FullOrthancDataset.cpp Wed May 17 22:03:09 2017 +0200 +++ b/Resources/Orthanc/Plugins/Samples/Common/FullOrthancDataset.cpp Thu May 18 17:51:41 2017 +0200 @@ -174,6 +174,13 @@ } + FullOrthancDataset::FullOrthancDataset(const Json::Value& root) : + root_(root) + { + CheckRoot(); + } + + bool FullOrthancDataset::GetStringValue(std::string& result, const DicomPath& path) const { diff -r 298f375dcb68 -r acb60cbb8301 Resources/Orthanc/Plugins/Samples/Common/FullOrthancDataset.h --- a/Resources/Orthanc/Plugins/Samples/Common/FullOrthancDataset.h Wed May 17 22:03:09 2017 +0200 +++ b/Resources/Orthanc/Plugins/Samples/Common/FullOrthancDataset.h Thu May 18 17:51:41 2017 +0200 @@ -58,6 +58,8 @@ FullOrthancDataset(const void* content, size_t size); + FullOrthancDataset(const Json::Value& root); + virtual bool GetStringValue(std::string& result, const DicomPath& path) const; diff -r 298f375dcb68 -r acb60cbb8301 UnitTestsSources/UnitTestsMain.cpp --- a/UnitTestsSources/UnitTestsMain.cpp Wed May 17 22:03:09 2017 +0200 +++ b/UnitTestsSources/UnitTestsMain.cpp Thu May 18 17:51:41 2017 +0200 @@ -27,44 +27,13 @@ #include "../Framework/Widgets/LayerWidget.h" +#include "../Framework/Toolbox/MessagingToolbox.h" +#include "../Framework/Toolbox/DicomFrameConverter.h" + namespace OrthancStone { - class Tata : public ILayerSource::IObserver - { - public: - virtual void NotifySourceChange(ILayerSource& source) - { - printf("Source change\n"); - - OrthancStone::SliceGeometry slice; - double x1, y1, x2, y2; - printf(">> %d: ", source.GetExtent(x1, y1, x2, y2, slice)); - printf("(%f,%f) (%f,%f)\n", x1, y1, x2, y2); - } - - virtual void NotifySliceChange(ILayerSource& source, - const SliceGeometry& slice) - { - printf("Slice change\n"); - } - - virtual void NotifyLayerReady(ILayerRenderer* layer, - ILayerSource& source, - const SliceGeometry& viewportSlice) - { - std::auto_ptr tmp(layer); - - } - - virtual void NotifyLayerError(ILayerSource& source, - const SliceGeometry& viewportSlice) - { - } - }; - - - - /*class OrthancInstanceLoader : public IWebService::ICallback + class SeriesLoader : + public IWebService::ICallback // TODO move to PImpl { public: class ICallback : public boost::noncopyable @@ -73,104 +42,394 @@ virtual ~ICallback() { } - - virtual void NotifyInstanceLoaded(const std::string& instanceId, - const OrthancPlugins::FullOrthancDataset& dicom) = 0; - - virtual void NotifyInstanceError(const std::string& instanceId) = 0; + + virtual void NotifyGeometryReady(const SeriesLoader& series) = 0; + + virtual void NotifyGeometryError(const SeriesLoader& series) = 0; }; - + private: - class Operation : public Orthanc::IDynamicObject + class Slice : public boost::noncopyable { private: - ICallback& callback_; - std::string instanceId_; - + std::string instanceId_; + SliceGeometry geometry_; + double thickness_; + unsigned int width_; + unsigned int height_; + double projectionAlongNormal_; + DicomFrameConverter converter_; + public: - Operation(ICallback& callback, - const std::string& instanceId) : - callback_(callback), - instanceId_(instanceId) + Slice(const std::string& instanceId, + const std::string& imagePositionPatient, + const std::string& imageOrientationPatient, + double thickness, + unsigned int width, + unsigned int height, + const OrthancPlugins::IDicomDataset& dataset) : + instanceId_(instanceId), + geometry_(imagePositionPatient, imageOrientationPatient), + thickness_(thickness), + width_(width), + height_(height) { + converter_.ReadParameters(dataset); + } + + const std::string GetInstanceId() const + { + return instanceId_; } - ICallback& GetCallback() + const SliceGeometry& GetGeometry() const + { + return geometry_; + } + + double GetThickness() const { - return callback_; + return thickness_; + } + + unsigned int GetWidth() const + { + return width_; } - const std::string& GetInstanceId() const + unsigned int GetHeight() const + { + return height_; + } + + const DicomFrameConverter& GetConverter() const { - return instanceId_; + return converter_; + } + + void SetNormal(const Vector& normal) + { + projectionAlongNormal_ = boost::numeric::ublas::inner_prod(geometry_.GetOrigin(), normal); + } + + double GetProjectionAlongNormal() const + { + return projectionAlongNormal_; } }; - IWebService& orthanc_; - public: - OrthancInstanceLoader(IWebService& orthanc) : - orthanc_(orthanc) + class SetOfSlices : public boost::noncopyable { - } + private: + std::vector slices_; + + struct Comparator + { + bool operator() (const Slice* const a, + const Slice* const b) const + { + return a->GetProjectionAlongNormal() < b->GetProjectionAlongNormal(); + } + }; + + public: + ~SetOfSlices() + { + for (size_t i = 0; i < slices_.size(); i++) + { + assert(slices_[i] != NULL); + delete slices_[i]; + } + } + + void Reserve(size_t size) + { + slices_.reserve(size); + } + + void AddSlice(const std::string& instanceId, + const Json::Value& value) + { + OrthancPlugins::FullOrthancDataset dataset(value); + OrthancPlugins::DicomDatasetReader reader(dataset); + + std::string position, orientation; + double thickness; + unsigned int width, height; + if (dataset.GetStringValue(position, OrthancPlugins::DICOM_TAG_IMAGE_POSITION_PATIENT) && + dataset.GetStringValue(orientation, OrthancPlugins::DICOM_TAG_IMAGE_ORIENTATION_PATIENT) && + reader.GetDoubleValue(thickness, OrthancPlugins::DICOM_TAG_SLICE_THICKNESS) && + reader.GetUnsignedIntegerValue(width, OrthancPlugins::DICOM_TAG_COLUMNS) && + reader.GetUnsignedIntegerValue(height, OrthancPlugins::DICOM_TAG_ROWS)); + { + slices_.push_back(new Slice(instanceId, position, orientation, + thickness, width, height, dataset)); + } + } + + size_t GetSliceCount() const + { + return slices_.size(); + } + + const Slice& GetSlice(size_t index) const + { + assert(slices_[index] != NULL); + return *slices_[index]; + } + + void Sort(const Vector& normal) + { + for (size_t i = 0; i < slices_.size(); i++) + { + slices_[i]->SetNormal(normal); + } + + Comparator comparator; + std::sort(slices_.begin(), slices_.end(), comparator); + } + + + void SelectNormal(Vector& normal) const + { + std::vector normalCandidates; + std::vector normalCount; + + bool found = false; + + for (size_t i = 0; !found && i < GetSliceCount(); i++) + { + const Vector& normal = GetSlice(i).GetGeometry().GetNormal(); + + bool add = true; + for (size_t j = 0; add && j < normalCandidates.size(); j++) // (*) + { + if (GeometryToolbox::IsParallel(normal, normalCandidates[j])) + { + normalCount[j] += 1; + add = false; + } + } + + if (add) + { + if (normalCount.size() > 2) + { + // To get linear-time complexity in (*). This heuristics + // allows the series to have one single frame that is + // not parallel to the others (such a frame could be a + // generated preview) + found = false; + } + else + { + normalCandidates.push_back(normal); + normalCount.push_back(1); + } + } + } - void ScheduleLoadInstance(ICallback& callback, - const std::string& instanceId) + for (size_t i = 0; !found && i < normalCandidates.size(); i++) + { + unsigned int count = normalCount[i]; + if (count == GetSliceCount() || + count + 1 == GetSliceCount()) + { + normal = normalCandidates[i]; + found = true; + } + } + + if (!found) + { + LOG(ERROR) << "Cannot select a normal that is shared by most of the slices of this series"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + } + + + void FilterNormal(const Vector& normal) + { + size_t pos = 0; + + for (size_t i = 0; i < slices_.size(); i++) + { + if (GeometryToolbox::IsParallel(normal, slices_[i]->GetGeometry().GetNormal())) + { + // This slice is compatible with the selected normal + slices_[pos] = slices_[i]; + pos += 1; + } + else + { + delete slices_[i]; + slices_[i] = NULL; + } + } + + slices_.resize(pos); + } + }; + + + enum Mode + { + Mode_Geometry + }; + + class Operation : public Orthanc::IDynamicObject { - orthanc_.ScheduleGetRequest(*this, - "/instances/" + instanceId + "/tags", - new Operation(callback, instanceId)); + private: + Mode mode_; + unsigned int instance_; + + Operation(Mode mode) : + mode_(mode) + { + } + + public: + Mode GetMode() const + { + return mode_; + } + + static Operation* DownloadGeometry() + { + return new Operation(Mode_Geometry); + } + }; + + ICallback& callback_; + IWebService& orthanc_; + std::string seriesId_; + SetOfSlices slices_; + + + void ParseGeometry(const void* answer, + size_t size) + { + Json::Value series; + if (!MessagingToolbox::ParseJson(series, answer, size) || + series.type() != Json::objectValue) + { + callback_.NotifyGeometryError(*this); + return; + } + + Json::Value::Members instances = series.getMemberNames(); + + slices_.Reserve(instances.size()); + + for (size_t i = 0; i < instances.size(); i++) + { + slices_.AddSlice(instances[i], series[instances[i]]); + } + + bool ok = false; + + if (slices_.GetSliceCount() > 0) + { + Vector normal; + slices_.SelectNormal(normal); + slices_.FilterNormal(normal); + slices_.Sort(normal); + ok = true; + } + + if (ok) + { + printf("%d\n", slices_.GetSliceCount()); + callback_.NotifyGeometryReady(*this); + } + else + { + LOG(ERROR) << "This series is empty"; + callback_.NotifyGeometryError(*this); + return; + } } - void NotifySuccess(const std::string& uri, - const void* answer, - size_t answerSize, - Orthanc::IDynamicObject* payload) + + public: + SeriesLoader(ICallback& callback, + IWebService& orthanc, + const std::string& seriesId) : + callback_(callback), + orthanc_(orthanc), + seriesId_(seriesId) { - std::auto_ptr operation(reinterpret_cast(payload)); + std::string uri = "/series/" + seriesId + "/instances-tags"; + orthanc.ScheduleGetRequest(*this, uri, Operation::DownloadGeometry()); + } - try + const std::string& GetSeriesId() const + { + return seriesId_; + } + + virtual void NotifySuccess(const std::string& uri, + const void* answer, + size_t answerSize, + Orthanc::IDynamicObject* payload) + { + std::auto_ptr operation(dynamic_cast(payload)); + + switch (operation->GetMode()) { - OrthancPlugins::FullOrthancDataset dataset(answer, answerSize); - operation->GetCallback().NotifyInstanceLoaded(operation->GetInstanceId(), dataset); - } - catch (Orthanc::OrthancException&) - { - operation->GetCallback().NotifyInstanceError(operation->GetInstanceId()); + case Mode_Geometry: + ParseGeometry(answer, answerSize); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); } } - - void NotifyError(const std::string& uri, - Orthanc::IDynamicObject* payload) + + virtual void NotifyError(const std::string& uri, + Orthanc::IDynamicObject* payload) { - std::auto_ptr operation(reinterpret_cast(payload)); - + std::auto_ptr operation(dynamic_cast(payload)); LOG(ERROR) << "Cannot download " << uri; - operation->GetCallback().NotifyInstanceError(operation->GetInstanceId()); + + switch (operation->GetMode()) + { + case Mode_Geometry: + callback_.NotifyGeometryError(*this); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } } - };*/ + }; + - + class Tata : public SeriesLoader::ICallback + { + public: + virtual void NotifyGeometryReady(const SeriesLoader& series) + { + printf("Done %s\n", series.GetSeriesId().c_str()); + } + virtual void NotifyGeometryError(const SeriesLoader& series) + { + printf("Error %s\n", series.GetSeriesId().c_str()); + } + }; } - TEST(Toto, Tutu) { Orthanc::WebServiceParameters web; OrthancStone::OrthancWebService orthanc(web); - OrthancStone::OrthancFrameLayerSource source(orthanc, "befb52a6-b4b04954-b5a019c3-fdada9d7-dddc9430", 0); OrthancStone::Tata tata; - source.SetObserver(tata); - - OrthancStone::SliceGeometry slice; - source.ScheduleLayerCreation(slice); - - - OrthancStone::LayerWidget widget; - printf(">> %d\n", widget.AddLayer(new OrthancStone::OrthancFrameLayerSource(orthanc, "befb52a6-b4b04954-b5a019c3-fdada9d7-dddc9430", 0))); + //OrthancStone::SeriesLoader loader(tata, orthanc, "c1c4cb95-05e3bd11-8da9f5bb-87278f71-0b2b43f5"); + OrthancStone::SeriesLoader loader(tata, orthanc, "67f1b334-02c16752-45026e40-a5b60b6b-030ecab5"); }