Mercurial > hg > orthanc-stone
view UnitTestsSources/UnitTestsMain.cpp @ 67:acb60cbb8301 wasm
refactoring SeriesLoader
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Thu, 18 May 2017 17:51:41 +0200 |
parents | 298f375dcb68 |
children | 1526d38ef6da |
line wrap: on
line source
/** * Stone of Orthanc * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium * Copyright (C) 2017 Osimis, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License * as published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. **/ #include "gtest/gtest.h" #include "../Resources/Orthanc/Core/Logging.h" #include "../Framework/Toolbox/OrthancWebService.h" #include "../Framework/Layers/OrthancFrameLayerSource.h" #include "../Framework/Widgets/LayerWidget.h" #include "../Framework/Toolbox/MessagingToolbox.h" #include "../Framework/Toolbox/DicomFrameConverter.h" namespace OrthancStone { class SeriesLoader : public IWebService::ICallback // TODO move to PImpl { public: class ICallback : public boost::noncopyable { public: virtual ~ICallback() { } virtual void NotifyGeometryReady(const SeriesLoader& series) = 0; virtual void NotifyGeometryError(const SeriesLoader& series) = 0; }; private: class Slice : public boost::noncopyable { private: std::string instanceId_; SliceGeometry geometry_; double thickness_; unsigned int width_; unsigned int height_; double projectionAlongNormal_; DicomFrameConverter converter_; public: 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_; } const SliceGeometry& GetGeometry() const { return geometry_; } double GetThickness() const { return thickness_; } unsigned int GetWidth() const { return width_; } unsigned int GetHeight() const { return height_; } const DicomFrameConverter& GetConverter() const { return converter_; } void SetNormal(const Vector& normal) { projectionAlongNormal_ = boost::numeric::ublas::inner_prod(geometry_.GetOrigin(), normal); } double GetProjectionAlongNormal() const { return projectionAlongNormal_; } }; class SetOfSlices : public boost::noncopyable { private: std::vector<Slice*> 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<Vector> normalCandidates; std::vector<unsigned int> 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); } } } 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 { 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; } } public: SeriesLoader(ICallback& callback, IWebService& orthanc, const std::string& seriesId) : callback_(callback), orthanc_(orthanc), seriesId_(seriesId) { std::string uri = "/series/" + seriesId + "/instances-tags"; orthanc.ScheduleGetRequest(*this, uri, Operation::DownloadGeometry()); } 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> operation(dynamic_cast<Operation*>(payload)); switch (operation->GetMode()) { case Mode_Geometry: ParseGeometry(answer, answerSize); break; default: throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); } } virtual void NotifyError(const std::string& uri, Orthanc::IDynamicObject* payload) { std::auto_ptr<Operation> operation(dynamic_cast<Operation*>(payload)); LOG(ERROR) << "Cannot download " << uri; 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::Tata tata; //OrthancStone::SeriesLoader loader(tata, orthanc, "c1c4cb95-05e3bd11-8da9f5bb-87278f71-0b2b43f5"); OrthancStone::SeriesLoader loader(tata, orthanc, "67f1b334-02c16752-45026e40-a5b60b6b-030ecab5"); } int main(int argc, char **argv) { Orthanc::Logging::Initialize(); Orthanc::Logging::EnableInfoLevel(true); ::testing::InitGoogleTest(&argc, argv); int result = RUN_ALL_TESTS(); Orthanc::Logging::Finalize(); return result; }