diff --git a/Modules/Core/include/mitkTemporoSpatialStringProperty.h b/Modules/Core/include/mitkTemporoSpatialStringProperty.h index 8f6f090f93..015a14320d 100644 --- a/Modules/Core/include/mitkTemporoSpatialStringProperty.h +++ b/Modules/Core/include/mitkTemporoSpatialStringProperty.h @@ -1,139 +1,148 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkTemporoSpatialStringProperty_h #define mitkTemporoSpatialStringProperty_h #include #include "mitkBaseProperty.h" #include #include "mitkTimeGeometry.h" #include namespace mitk { #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable : 4522) #endif /** * @brief Property for time and space resolved string values * @ingroup DataManagement */ class MITKCORE_EXPORT TemporoSpatialStringProperty : public BaseProperty { public: typedef ::itk::IndexValueType IndexValueType; typedef std::string ValueType; mitkClassMacro(TemporoSpatialStringProperty, BaseProperty); itkFactorylessNewMacro(Self); itkCloneMacro(Self); mitkNewMacro1Param(TemporoSpatialStringProperty, const char*); mitkNewMacro1Param(TemporoSpatialStringProperty, const std::string &); /**Returns the value of the first time point in the first slice. * If now value is set it returns an empty string.*/ ValueType GetValue() const; /**Returns the value of the passed time step and slice. If it does not exist and allowedClosed is true * it will look for the closest value. If nothing could be found an empty string will be returned.*/ ValueType GetValue(const TimeStepType &timeStep, const IndexValueType &zSlice, bool allowCloseTime = false, bool allowCloseSlice = false) const; ValueType GetValueBySlice(const IndexValueType &zSlice, bool allowClose = false) const; ValueType GetValueByTimeStep(const TimeStepType &timeStep, bool allowClose = false) const; bool HasValue() const; bool HasValue(const TimeStepType &timeStep, const IndexValueType &zSlice, bool allowCloseTime = false, bool allowCloseSlice = false) const; bool HasValueBySlice(const IndexValueType &zSlice, bool allowClose = false) const; bool HasValueByTimeStep(const TimeStepType &timeStep, bool allowClose = false) const; /** return all slices stored for the specified timestep.*/ std::vector GetAvailableSlices(const TimeStepType& timeStep) const; /** return all time steps stored for the specified slice.*/ std::vector GetAvailableTimeSteps(const IndexValueType& slice) const; /** return all time steps stored in the property.*/ std::vector GetAvailableTimeSteps() const; /** return all slices stored in the property. @remark not all time steps may contain all slices.*/ std::vector GetAvailableSlices() const; void SetValue(const TimeStepType &timeStep, const IndexValueType &zSlice, const ValueType &value); void SetValue(const ValueType &value); std::string GetValueAsString() const override; /** Indicates of all values (all time steps, all slices) are the same, or if at least one value stored in the property is different. If IsUniform==true one can i.a. use GetValueAsString() without the loss of information to retrieve the stored value.*/ bool IsUniform() const; bool ToJSON(nlohmann::json& j) const override; bool FromJSON(const nlohmann::json& j) override; using BaseProperty::operator=; protected: typedef std::map SliceMapType; typedef std::map TimeMapType; TimeMapType m_Values; TemporoSpatialStringProperty(const char *string = nullptr); TemporoSpatialStringProperty(const std::string &s); TemporoSpatialStringProperty(const TemporoSpatialStringProperty &); std::pair CheckValue(const TimeStepType &timeStep, const IndexValueType &zSlice, bool allowCloseTime = false, bool allowCloseSlice = false) const; private: // purposely not implemented TemporoSpatialStringProperty &operator=(const TemporoSpatialStringProperty &); itk::LightObject::Pointer InternalClone() const override; bool IsEqual(const BaseProperty &property) const override; bool Assign(const BaseProperty &property) override; }; namespace PropertyPersistenceSerialization { /** Serialization of a TemporoSpatialStringProperty into a JSON string.*/ MITKCORE_EXPORT std::string serializeTemporoSpatialStringPropertyToJSON(const mitk::BaseProperty *prop); } namespace PropertyPersistenceDeserialization { /**Deserialize a passed JSON string into a TemporoSpatialStringProperty.*/ MITKCORE_EXPORT mitk::BaseProperty::Pointer deserializeJSONToTemporoSpatialStringProperty(const std::string &value); } + /** Helper function that extracts the information of a time step out of a TemporoSpatialStringProperty + * and returns a TemporoSpatialStringProperty that only contains that time step. + * @param tsProperty The source property from which the values should be extracted. + * @param ts The time point that should be extracted. + * @pre tsProperty must point to a valid instance. + * @pre ts must indicate a time step that exists in tsProperty. + * @result Returns a TemporoSpatialStringProperty instance that only contains the values of the indicated time step. In the result the time step is always time step 0.*/ + TemporoSpatialStringProperty::Pointer MITKCORE_EXPORT ExtractTimeStepFromTemporoSpatialStringProperty(const TemporoSpatialStringProperty* tsProperty, TimeStepType ts); + #ifdef _MSC_VER #pragma warning(pop) #endif } // namespace mitk #endif diff --git a/Modules/Core/src/DataManagement/mitkTemporoSpatialStringProperty.cpp b/Modules/Core/src/DataManagement/mitkTemporoSpatialStringProperty.cpp index ac53fc9dd6..99501017ba 100644 --- a/Modules/Core/src/DataManagement/mitkTemporoSpatialStringProperty.cpp +++ b/Modules/Core/src/DataManagement/mitkTemporoSpatialStringProperty.cpp @@ -1,425 +1,444 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include #include #include #include "mitkTemporoSpatialStringProperty.h" #include using CondensedTimeKeyType = std::pair; using CondensedTimePointsType = std::map; using CondensedSliceKeyType = std::pair; using CondensedSlicesType = std::map; namespace { /** Helper function that checks if between an ID and a successive ID is no gap.*/ template bool isGap(const TValue& value, const TValue& successor) { return value successor + 1; } template void CheckAndCondenseElement(const TNewKey& newKeyMinID, const TNewValue& newValue, TMasterKey& masterKey, TMasterValue& masterValue, TCondensedContainer& condensedContainer) { if (newValue != masterValue || isGap(newKeyMinID, masterKey.second)) { condensedContainer[masterKey] = masterValue; masterValue = newValue; masterKey.first = newKeyMinID; } masterKey.second = newKeyMinID; } /** Helper function that tries to condense the values of time points for a slice as much as possible and returns all slices with condensed timepoint values.*/ CondensedSlicesType CondenseTimePointValuesOfProperty(const mitk::TemporoSpatialStringProperty* tsProp) { CondensedSlicesType uncondensedSlices; auto zs = tsProp->GetAvailableSlices(); for (const auto z : zs) { CondensedTimePointsType condensedTimePoints; auto timePointIDs = tsProp->GetAvailableTimeSteps(z); CondensedTimeKeyType condensedKey = { timePointIDs.front(),timePointIDs.front() }; auto refValue = tsProp->GetValue(timePointIDs.front(), z); for (const auto timePointID : timePointIDs) { const auto& newVal = tsProp->GetValue(timePointID, z); CheckAndCondenseElement(timePointID, newVal, condensedKey, refValue, condensedTimePoints); } condensedTimePoints[condensedKey] = refValue; uncondensedSlices[{ z, z }] = condensedTimePoints; } return uncondensedSlices; } } mitk::TemporoSpatialStringProperty::TemporoSpatialStringProperty(const char *s) { if (s) { SliceMapType slices{{0, s}}; m_Values.insert(std::make_pair(0, slices)); } } mitk::TemporoSpatialStringProperty::TemporoSpatialStringProperty(const std::string &s) { SliceMapType slices{{0, s}}; m_Values.insert(std::make_pair(0, slices)); } mitk::TemporoSpatialStringProperty::TemporoSpatialStringProperty(const TemporoSpatialStringProperty &other) : BaseProperty(other), m_Values(other.m_Values) { } bool mitk::TemporoSpatialStringProperty::IsEqual(const BaseProperty &property) const { return this->m_Values == static_cast(property).m_Values; } bool mitk::TemporoSpatialStringProperty::Assign(const BaseProperty &property) { this->m_Values = static_cast(property).m_Values; return true; } std::string mitk::TemporoSpatialStringProperty::GetValueAsString() const { return GetValue(); } bool mitk::TemporoSpatialStringProperty::IsUniform() const { auto refValue = this->GetValue(); for (const auto& timeStep : m_Values) { auto finding = std::find_if_not(timeStep.second.begin(), timeStep.second.end(), [&refValue](const mitk::TemporoSpatialStringProperty::SliceMapType::value_type& val) { return val.second == refValue; }); if (finding != timeStep.second.end()) { return false; } } return true; } itk::LightObject::Pointer mitk::TemporoSpatialStringProperty::InternalClone() const { itk::LightObject::Pointer result(new Self(*this)); result->UnRegister(); return result; } mitk::TemporoSpatialStringProperty::ValueType mitk::TemporoSpatialStringProperty::GetValue() const { std::string result = ""; if (!m_Values.empty()) { if (!m_Values.begin()->second.empty()) { result = m_Values.begin()->second.begin()->second; } } return result; }; std::pair mitk::TemporoSpatialStringProperty::CheckValue( const TimeStepType &timeStep, const IndexValueType &zSlice, bool allowCloseTime, bool allowCloseSlice) const { std::string value = ""; bool found = false; auto timeIter = m_Values.find(timeStep); auto timeEnd = m_Values.end(); if (timeIter == timeEnd && allowCloseTime) { // search for closest time step (earlier preverd) timeIter = m_Values.upper_bound(timeStep); if (timeIter != m_Values.begin()) { // there is a key lower than time step timeIter = std::prev(timeIter); } } if (timeIter != timeEnd) { const SliceMapType &slices = timeIter->second; auto sliceIter = slices.find(zSlice); auto sliceEnd = slices.end(); if (sliceIter == sliceEnd && allowCloseSlice) { // search for closest slice (earlier preverd) sliceIter = slices.upper_bound(zSlice); if (sliceIter != slices.begin()) { // there is a key lower than slice sliceIter = std::prev(sliceIter); } } if (sliceIter != sliceEnd) { value = sliceIter->second; found = true; } } return std::make_pair(found, value); }; mitk::TemporoSpatialStringProperty::ValueType mitk::TemporoSpatialStringProperty::GetValue(const TimeStepType &timeStep, const IndexValueType &zSlice, bool allowCloseTime, bool allowCloseSlice) const { return CheckValue(timeStep, zSlice, allowCloseTime, allowCloseSlice).second; }; mitk::TemporoSpatialStringProperty::ValueType mitk::TemporoSpatialStringProperty::GetValueBySlice( const IndexValueType &zSlice, bool allowClose) const { return GetValue(0, zSlice, true, allowClose); }; mitk::TemporoSpatialStringProperty::ValueType mitk::TemporoSpatialStringProperty::GetValueByTimeStep( const TimeStepType &timeStep, bool allowClose) const { return GetValue(timeStep, 0, allowClose, true); }; bool mitk::TemporoSpatialStringProperty::HasValue() const { return !m_Values.empty(); }; bool mitk::TemporoSpatialStringProperty::HasValue(const TimeStepType &timeStep, const IndexValueType &zSlice, bool allowCloseTime, bool allowCloseSlice) const { return CheckValue(timeStep, zSlice, allowCloseTime, allowCloseSlice).first; }; bool mitk::TemporoSpatialStringProperty::HasValueBySlice(const IndexValueType &zSlice, bool allowClose) const { return HasValue(0, zSlice, true, allowClose); }; bool mitk::TemporoSpatialStringProperty::HasValueByTimeStep(const TimeStepType &timeStep, bool allowClose) const { return HasValue(timeStep, 0, allowClose, true); }; std::vector mitk::TemporoSpatialStringProperty::GetAvailableSlices() const { std::set uniqueSlices; for (const auto& timeStep : m_Values) { for (const auto& slice : timeStep.second) { uniqueSlices.insert(slice.first); } } return std::vector(std::begin(uniqueSlices), std::end(uniqueSlices)); } std::vector mitk::TemporoSpatialStringProperty::GetAvailableSlices( const TimeStepType &timeStep) const { std::vector result; auto timeIter = m_Values.find(timeStep); auto timeEnd = m_Values.end(); if (timeIter != timeEnd) { for (auto const &element : timeIter->second) { result.push_back(element.first); } } return result; }; std::vector mitk::TemporoSpatialStringProperty::GetAvailableTimeSteps() const { std::vector result; for (auto const &element : m_Values) { result.push_back(element.first); } return result; }; std::vector mitk::TemporoSpatialStringProperty::GetAvailableTimeSteps(const IndexValueType& slice) const { std::vector result; for (const auto& timeStep : m_Values) { if (timeStep.second.find(slice) != std::end(timeStep.second)) { result.push_back(timeStep.first); } } return result; } void mitk::TemporoSpatialStringProperty::SetValue(const TimeStepType &timeStep, const IndexValueType &zSlice, const ValueType &value) { auto timeIter = m_Values.find(timeStep); auto timeEnd = m_Values.end(); if (timeIter == timeEnd) { SliceMapType slices{{zSlice, value}}; m_Values.insert(std::make_pair(timeStep, slices)); } else { timeIter->second[zSlice] = value; } this->Modified(); }; void mitk::TemporoSpatialStringProperty::SetValue(const ValueType &value) { m_Values.clear(); this->SetValue(0, 0, value); }; bool mitk::TemporoSpatialStringProperty::ToJSON(nlohmann::json& j) const { // We condense the content of the property to have a compact serialization. // We start with condensing time points and then slices (in difference to the // internal layout). Reason: There is more entropy in slices (looking at DICOM) // than across time points for one slice, so we can "compress" at a higher rate. // We didn't want to change the internal structure of the property as it would // introduce API inconvenience and subtle changes in behavior. auto uncondensedSlices = CondenseTimePointValuesOfProperty(this); CondensedSlicesType condensedSlices; if(!uncondensedSlices.empty()) { auto& masterSlice = uncondensedSlices.begin()->second; auto masterSliceKey = uncondensedSlices.begin()->first; for (const auto& uncondensedSlice : uncondensedSlices) { const auto& uncondensedSliceID = uncondensedSlice.first.first; CheckAndCondenseElement(uncondensedSliceID, uncondensedSlice.second, masterSliceKey, masterSlice, condensedSlices); } condensedSlices[masterSliceKey] = masterSlice; } auto values = nlohmann::json::array(); for (const auto& z : condensedSlices) { for (const auto& t : z.second) { const auto& minSliceID = z.first.first; const auto& maxSliceID = z.first.second; const auto& minTimePointID = t.first.first; const auto& maxTimePointID = t.first.second; auto value = nlohmann::json::object(); value["z"] = minSliceID; if (minSliceID != maxSliceID) value["zmax"] = maxSliceID; value["t"] = minTimePointID; if (minTimePointID != maxTimePointID) value["tmax"] = maxTimePointID; value["value"] = t.second; values.push_back(value); } } j = nlohmann::json{{"values", values}}; return true; } bool mitk::TemporoSpatialStringProperty::FromJSON(const nlohmann::json& j) { for (const auto& element : j["values"]) { auto value = element.value("value", ""); auto z = element.value("z", 0); auto zmax = element.value("zmax", z); auto t = element.value("t", 0); auto tmax = element.value("tmax", t); for (auto currentT = t; currentT <= tmax; ++currentT) { for (auto currentZ = z; currentZ <= zmax; ++currentZ) { this->SetValue(currentT, currentZ, value); } } } return true; } std::string mitk::PropertyPersistenceSerialization::serializeTemporoSpatialStringPropertyToJSON(const mitk::BaseProperty *prop) { const auto *tsProp = dynamic_cast(prop); if (!tsProp) mitkThrow() << "Cannot serialize properties of types other than TemporoSpatialStringProperty."; nlohmann::json j; tsProp->ToJSON(j); return j.dump(); } mitk::BaseProperty::Pointer mitk::PropertyPersistenceDeserialization::deserializeJSONToTemporoSpatialStringProperty(const std::string &value) { if (value.empty()) return nullptr; auto prop = mitk::TemporoSpatialStringProperty::New(); auto root = nlohmann::json::parse(value); prop->FromJSON(root); return prop.GetPointer(); } + +mitk::TemporoSpatialStringProperty::Pointer mitk::ExtractTimeStepFromTemporoSpatialStringProperty(const TemporoSpatialStringProperty* tsProperty, TimeStepType ts) +{ + if (nullptr == tsProperty) + mitkThrow() << "Cannot extract time step. Passed TemporoSpatialStringProperty pointer is invalid."; + + if (!tsProperty->HasValueByTimeStep(ts)) + mitkThrow() << "Cannot extract time step. TemporoSpatialStringProperty does not contain values for that passed time step. Invalid time step: " << ts; + + auto slices = tsProperty->GetAvailableSlices(ts); + + auto result = mitk::TemporoSpatialStringProperty::New(); + + for (const auto& slice : slices) + { + result->SetValue(0, slice, tsProperty->GetValue(ts, slice)); + } + return result; +} diff --git a/Modules/Core/test/mitkTemporoSpatialStringPropertyTest.cpp b/Modules/Core/test/mitkTemporoSpatialStringPropertyTest.cpp index 48db4613df..29c758999d 100644 --- a/Modules/Core/test/mitkTemporoSpatialStringPropertyTest.cpp +++ b/Modules/Core/test/mitkTemporoSpatialStringPropertyTest.cpp @@ -1,245 +1,273 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkTemporoSpatialStringProperty.h" #include "mitkTestFixture.h" #include "mitkTestingMacros.h" #include class mitkTemporoSpatialStringPropertyTestSuite : public mitk::TestFixture { CPPUNIT_TEST_SUITE(mitkTemporoSpatialStringPropertyTestSuite); MITK_TEST(GetValue); MITK_TEST(HasValue); MITK_TEST(SetValue); MITK_TEST(IsUniform); MITK_TEST(serializeTemporoSpatialStringPropertyToJSON); MITK_TEST(deserializeJSONToTemporoSpatialStringProperty); + MITK_TEST(ExtractTimeStepFromTemporoSpatialStringProperty); + CPPUNIT_TEST_SUITE_END(); private: std::string refJSON; std::string refJSON_legacy; std::string refPartlyCondensibleJSON; std::string refCondensibleJSON; mitk::TemporoSpatialStringProperty::Pointer refProp; mitk::TemporoSpatialStringProperty::Pointer refPartlyCondensibleProp; mitk::TemporoSpatialStringProperty::Pointer refCondensibleProp; public: void setUp() override { refJSON_legacy = "{\"values\":[{\"t\":0, \"z\":0, \"value\":\"v_0_0\"}, {\"t\":3, \"z\":0, \"value\":\"v_3_0\"}, " "{\"t\":3, \"z\":2, \"value\":\"v_3_2\"}, {\"t\":6, \"z\":1, \"value\":\"v_6_1\"}]}"; refJSON = "{\"values\":[{\"z\":0, \"t\":0, \"value\":\"v_0_0\"}, {\"z\":0, \"t\":3, \"value\":\"v_3_0\"}, {\"z\":1, \"t\":6, \"value\":\"v_6_1\"}, {\"z\":2, \"t\":3, \"value\":\"v_3_2\"}]}"; refPartlyCondensibleJSON = "{\"values\":[{\"z\":0, \"t\":0, \"value\":\"0\"}, {\"z\":1, \"t\":0, \"tmax\":1, \"value\":\"0\"}, {\"z\":1, \"t\":3, \"tmax\":5, \"value\":\"0\"}, {\"z\":1, \"t\":6, \"value\":\"otherValue\"}, {\"z\":2, \"t\":6, \"value\":\"0\"}]}"; refCondensibleJSON = "{\"values\":[{\"z\":0, \"zmax\":2, \"t\":0, \"tmax\":1, \"value\":\"1\"}]}"; refProp = mitk::TemporoSpatialStringProperty::New(); refProp->SetValue(0, 0, "v_0_0"); refProp->SetValue(3, 0, "v_3_0"); refProp->SetValue(3, 2, "v_3_2"); refProp->SetValue(6, 1, "v_6_1"); refPartlyCondensibleProp = mitk::TemporoSpatialStringProperty::New(); refPartlyCondensibleProp->SetValue(0, 0, "0"); refPartlyCondensibleProp->SetValue(0, 1, "0"); refPartlyCondensibleProp->SetValue(1, 1, "0"); refPartlyCondensibleProp->SetValue(3, 1, "0"); refPartlyCondensibleProp->SetValue(4, 1, "0"); refPartlyCondensibleProp->SetValue(5, 1, "0"); refPartlyCondensibleProp->SetValue(6, 1, "otherValue"); refPartlyCondensibleProp->SetValue(6, 2, "0"); refCondensibleProp = mitk::TemporoSpatialStringProperty::New(); refCondensibleProp->SetValue(0, 0, "1"); refCondensibleProp->SetValue(1, 0, "1"); refCondensibleProp->SetValue(0, 1, "1"); refCondensibleProp->SetValue(1, 1, "1"); refCondensibleProp->SetValue(0, 2, "1"); refCondensibleProp->SetValue(1, 2, "1"); } void tearDown() override {} void GetValue() { CPPUNIT_ASSERT(refProp->GetValue() == "v_0_0"); CPPUNIT_ASSERT(refProp->GetValue(3, 0) == "v_3_0"); CPPUNIT_ASSERT(refProp->GetValue(3, 2) == "v_3_2"); CPPUNIT_ASSERT(refProp->GetValue(3, 1, false, true) == "v_3_0"); CPPUNIT_ASSERT(refProp->GetValue(3, 5, false, true) == "v_3_2"); CPPUNIT_ASSERT(refProp->GetValueBySlice(0) == "v_0_0"); CPPUNIT_ASSERT(refProp->GetValueBySlice(4, true) == "v_0_0"); CPPUNIT_ASSERT(refProp->GetValueByTimeStep(3) == "v_3_0"); CPPUNIT_ASSERT(refProp->GetValueByTimeStep(6) == "v_6_1"); CPPUNIT_ASSERT(refProp->GetValueByTimeStep(5, true) == "v_3_0"); CPPUNIT_ASSERT(refProp->GetValueAsString() == "v_0_0"); CPPUNIT_ASSERT(refProp->GetAvailableTimeSteps().size() == 3); CPPUNIT_ASSERT(refProp->GetAvailableTimeSteps()[0] == 0); CPPUNIT_ASSERT(refProp->GetAvailableTimeSteps()[1] == 3); CPPUNIT_ASSERT(refProp->GetAvailableTimeSteps()[2] == 6); CPPUNIT_ASSERT(refProp->GetAvailableTimeSteps(0).size() == 2); CPPUNIT_ASSERT(refProp->GetAvailableTimeSteps(0)[0] == 0); CPPUNIT_ASSERT(refProp->GetAvailableTimeSteps(0)[1] == 3); CPPUNIT_ASSERT(refProp->GetAvailableTimeSteps(1).size() == 1); CPPUNIT_ASSERT(refProp->GetAvailableTimeSteps(1)[0] == 6); CPPUNIT_ASSERT(refProp->GetAvailableTimeSteps(5).size() == 0); CPPUNIT_ASSERT(refProp->GetAvailableSlices().size() == 3); CPPUNIT_ASSERT(refProp->GetAvailableSlices()[0] == 0); CPPUNIT_ASSERT(refProp->GetAvailableSlices()[1] == 1); CPPUNIT_ASSERT(refProp->GetAvailableSlices()[2] == 2); CPPUNIT_ASSERT(refProp->GetAvailableSlices(0).size() == 1); CPPUNIT_ASSERT(refProp->GetAvailableSlices(0)[0] == 0); CPPUNIT_ASSERT(refProp->GetAvailableSlices(3).size() == 2); CPPUNIT_ASSERT(refProp->GetAvailableSlices(3)[0] == 0); CPPUNIT_ASSERT(refProp->GetAvailableSlices(3)[1] == 2); CPPUNIT_ASSERT(refProp->GetAvailableSlices(2).size() == 0); } void HasValue() { CPPUNIT_ASSERT(refProp->HasValue()); CPPUNIT_ASSERT(refProp->HasValue(3, 0)); CPPUNIT_ASSERT(refProp->HasValue(3, 2)); CPPUNIT_ASSERT(refProp->HasValue(3, 1, false, true)); CPPUNIT_ASSERT(refProp->HasValue(3, 5, false, true)); CPPUNIT_ASSERT(!refProp->HasValue(3, 1)); CPPUNIT_ASSERT(!refProp->HasValue(3, 5)); CPPUNIT_ASSERT(refProp->HasValue(4, 2, true, true)); CPPUNIT_ASSERT(refProp->HasValue(4, 2, true, false)); CPPUNIT_ASSERT(!refProp->HasValue(4, 2, false, true)); CPPUNIT_ASSERT(refProp->HasValueBySlice(0)); CPPUNIT_ASSERT(refProp->HasValueBySlice(4, true)); CPPUNIT_ASSERT(!refProp->HasValueBySlice(4)); CPPUNIT_ASSERT(refProp->HasValueByTimeStep(3)); CPPUNIT_ASSERT(refProp->HasValueByTimeStep(6)); CPPUNIT_ASSERT(refProp->HasValueByTimeStep(5, true)); CPPUNIT_ASSERT(!refProp->HasValueByTimeStep(5)); } void SetValue() { CPPUNIT_ASSERT_NO_THROW(refProp->SetValue(8, 9, "v_8_9")); CPPUNIT_ASSERT(refProp->GetValue(8, 9) == "v_8_9"); CPPUNIT_ASSERT_NO_THROW(refProp->SetValue("newValue")); CPPUNIT_ASSERT(refProp->GetValue(0, 0) == "newValue"); CPPUNIT_ASSERT(refProp->GetAvailableTimeSteps().size() == 1); CPPUNIT_ASSERT(refProp->GetAvailableSlices(0).size() == 1); } void IsUniform() { CPPUNIT_ASSERT(!refProp->IsUniform()); CPPUNIT_ASSERT(!refPartlyCondensibleProp->IsUniform()); CPPUNIT_ASSERT(refCondensibleProp->IsUniform()); } void serializeTemporoSpatialStringPropertyToJSON() { auto data = nlohmann::json::parse(mitk::PropertyPersistenceSerialization::serializeTemporoSpatialStringPropertyToJSON(refProp)); auto ref = nlohmann::json::parse(refJSON); CPPUNIT_ASSERT(ref == data); //"Testing serializeTemporoSpatialStringPropertyToJSON() producing correct string."); data = nlohmann::json::parse(mitk::PropertyPersistenceSerialization::serializeTemporoSpatialStringPropertyToJSON(refPartlyCondensibleProp)); ref = nlohmann::json::parse(refPartlyCondensibleJSON); CPPUNIT_ASSERT(ref == data); data = nlohmann::json::parse(mitk::PropertyPersistenceSerialization::serializeTemporoSpatialStringPropertyToJSON(refCondensibleProp)); ref = nlohmann::json::parse(refCondensibleJSON); CPPUNIT_ASSERT(ref == data); } void deserializeJSONToTemporoSpatialStringProperty() { mitk::BaseProperty::Pointer prop = mitk::PropertyPersistenceDeserialization::deserializeJSONToTemporoSpatialStringProperty(refJSON); auto *tsProp = dynamic_cast(prop.GetPointer()); CPPUNIT_ASSERT( tsProp->GetValue(0, 0) == "v_0_0"); //"Testing deserializeJSONToTemporoSpatialStringProperty() producing property with correct value 1"); CPPUNIT_ASSERT( tsProp->GetValue(3, 0) == "v_3_0"); //"Testing deserializeJSONToTemporoSpatialStringProperty() producing property with correct value 2"); CPPUNIT_ASSERT( tsProp->GetValue(3, 2) == "v_3_2"); //"Testing deserializeJSONToTemporoSpatialStringProperty() producing property with correct value 3"); CPPUNIT_ASSERT( tsProp->GetValue(6, 1) == "v_6_1"); //"Testing deserializeJSONToTemporoSpatialStringProperty() producing property with correct value 4"); CPPUNIT_ASSERT(*tsProp == *refProp); //"Testing deserializeJSONToTemporoSpatialStringProperty()"); prop = mitk::PropertyPersistenceDeserialization::deserializeJSONToTemporoSpatialStringProperty(refJSON_legacy); tsProp = dynamic_cast(prop.GetPointer()); CPPUNIT_ASSERT( tsProp->GetValue(0, 0) == "v_0_0"); //"Testing deserializeJSONToTemporoSpatialStringProperty() producing property with correct value 1"); CPPUNIT_ASSERT( tsProp->GetValue(3, 0) == "v_3_0"); //"Testing deserializeJSONToTemporoSpatialStringProperty() producing property with correct value 2"); CPPUNIT_ASSERT( tsProp->GetValue(3, 2) == "v_3_2"); //"Testing deserializeJSONToTemporoSpatialStringProperty() producing property with correct value 3"); CPPUNIT_ASSERT( tsProp->GetValue(6, 1) == "v_6_1"); //"Testing deserializeJSONToTemporoSpatialStringProperty() producing property with correct value 4"); CPPUNIT_ASSERT(*tsProp == *refProp); //"Testing deserializeJSONToTemporoSpatialStringProperty()"); prop = mitk::PropertyPersistenceDeserialization::deserializeJSONToTemporoSpatialStringProperty(refPartlyCondensibleJSON); tsProp = dynamic_cast(prop.GetPointer()); CPPUNIT_ASSERT(tsProp->GetValue(0, 0) =="0"); CPPUNIT_ASSERT(tsProp->GetValue(0, 1) == "0"); CPPUNIT_ASSERT(tsProp->GetValue(1, 1) == "0"); CPPUNIT_ASSERT(tsProp->GetValue(3, 1) == "0"); CPPUNIT_ASSERT(tsProp->GetValue(4, 1) == "0"); CPPUNIT_ASSERT(tsProp->GetValue(5, 1) == "0"); CPPUNIT_ASSERT(tsProp->GetValue(6, 1) == "otherValue"); CPPUNIT_ASSERT(tsProp->GetValue(6, 2) == "0"); CPPUNIT_ASSERT(*tsProp == *refPartlyCondensibleProp); prop = mitk::PropertyPersistenceDeserialization::deserializeJSONToTemporoSpatialStringProperty(refCondensibleJSON); tsProp = dynamic_cast(prop.GetPointer()); CPPUNIT_ASSERT(tsProp->GetValue(0, 0) == "1"); CPPUNIT_ASSERT(tsProp->GetValue(1, 0) == "1"); CPPUNIT_ASSERT(tsProp->GetValue(0, 1) == "1"); CPPUNIT_ASSERT(tsProp->GetValue(1, 1) == "1"); CPPUNIT_ASSERT(tsProp->GetValue(0, 2) == "1"); CPPUNIT_ASSERT(tsProp->GetValue(1, 2) == "1"); CPPUNIT_ASSERT(*tsProp == *refCondensibleProp); } + + void ExtractTimeStepFromTemporoSpatialStringProperty() + { + CPPUNIT_ASSERT_THROW(mitk::ExtractTimeStepFromTemporoSpatialStringProperty(nullptr, 0), mitk::Exception); + CPPUNIT_ASSERT_THROW_MESSAGE("Test for accessing invalid time step failed.", mitk::ExtractTimeStepFromTemporoSpatialStringProperty(refProp, 4), mitk::Exception); + + auto result = mitk::ExtractTimeStepFromTemporoSpatialStringProperty(refProp, 0); + + CPPUNIT_ASSERT(result->GetAvailableTimeSteps().size() == 1); + CPPUNIT_ASSERT(result->GetAvailableSlices(0).size() == 1); + CPPUNIT_ASSERT(result->GetValue(0, 0) == "v_0_0"); + + result = mitk::ExtractTimeStepFromTemporoSpatialStringProperty(refProp, 3); + + CPPUNIT_ASSERT(result->GetAvailableTimeSteps().size() == 1); + CPPUNIT_ASSERT(result->GetAvailableSlices(0).size() == 2); + CPPUNIT_ASSERT(result->GetValue(0, 0) == "v_3_0"); + CPPUNIT_ASSERT(result->GetValue(0, 2) == "v_3_2"); + + result = mitk::ExtractTimeStepFromTemporoSpatialStringProperty(refProp, 6); + + CPPUNIT_ASSERT(result->GetAvailableTimeSteps().size() == 1); + CPPUNIT_ASSERT(result->GetAvailableSlices(0).size() == 1); + CPPUNIT_ASSERT(result->GetValue(0, 1) == "v_6_1"); + + } }; MITK_TEST_SUITE_REGISTRATION(mitkTemporoSpatialStringProperty) diff --git a/Modules/ModelFit/cmdapps/CMakeLists.txt b/Modules/ModelFit/cmdapps/CMakeLists.txt index 9ba9628919..17f5096d03 100644 --- a/Modules/ModelFit/cmdapps/CMakeLists.txt +++ b/Modules/ModelFit/cmdapps/CMakeLists.txt @@ -1,34 +1,35 @@ option(BUILD_ModelFitMiniApps "Build commandline tools for the ModelFit module" OFF) if(BUILD_ModelFitMiniApps OR MITK_BUILD_ALL_APPS) # needed include directories include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ) # list of miniapps # if an app requires additional dependencies # they are added after a "^^" and separated by "_" set( miniapps - GenericFittingMiniApp^^ - PixelDumpMiniApp^^ - Fuse3Dto4DImageMiniApp^^ + GenericFitting^^ + PixelDump^^ + Fuse3Dto4DImage^^ + Split4Dto3DImages^^ ) foreach(miniapp ${miniapps}) # extract mini app name and dependencies string(REPLACE "^^" "\\;" miniapp_info ${miniapp}) set(miniapp_info_list ${miniapp_info}) list(GET miniapp_info_list 0 appname) list(GET miniapp_info_list 1 raw_dependencies) string(REPLACE "_" "\\;" dependencies "${raw_dependencies}") set(dependencies_list ${dependencies}) mitkFunctionCreateCommandLineApp( NAME ${appname} DEPENDS MitkCore MitkModelFit ${dependencies_list} ) endforeach() endif(BUILD_ModelFitMiniApps OR MITK_BUILD_ALL_APPS) diff --git a/Modules/ModelFit/cmdapps/Fuse3Dto4DImageMiniApp.cpp b/Modules/ModelFit/cmdapps/Fuse3Dto4DImage.cpp similarity index 100% rename from Modules/ModelFit/cmdapps/Fuse3Dto4DImageMiniApp.cpp rename to Modules/ModelFit/cmdapps/Fuse3Dto4DImage.cpp diff --git a/Modules/ModelFit/cmdapps/GenericFittingMiniApp.cpp b/Modules/ModelFit/cmdapps/GenericFitting.cpp similarity index 100% rename from Modules/ModelFit/cmdapps/GenericFittingMiniApp.cpp rename to Modules/ModelFit/cmdapps/GenericFitting.cpp diff --git a/Modules/ModelFit/cmdapps/PixelDumpMiniApp.cpp b/Modules/ModelFit/cmdapps/PixelDump.cpp similarity index 100% rename from Modules/ModelFit/cmdapps/PixelDumpMiniApp.cpp rename to Modules/ModelFit/cmdapps/PixelDump.cpp diff --git a/Modules/ModelFit/cmdapps/Split4Dto3DImages.cpp b/Modules/ModelFit/cmdapps/Split4Dto3DImages.cpp new file mode 100644 index 0000000000..7ffe9524b3 --- /dev/null +++ b/Modules/ModelFit/cmdapps/Split4Dto3DImages.cpp @@ -0,0 +1,170 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +// std includes +#include +#include + +// itk includes +#include "itksys/SystemTools.hxx" + +// CTK includes +#include "mitkCommandLineParser.h" + +// MITK includes +#include +#include +#include +#include + +std::string inFileName; +std::string outFileNamePattern; + +void setupParser(mitkCommandLineParser& parser) +{ + // set general information + parser.setCategory("Dynamic Data Analysis Tools"); + parser.setTitle("Split 4D to 3D Image"); + parser.setDescription("CLI app that allows to split an image into the images per time point. Time resolved properties will be also split."); + parser.setContributor("DKFZ MIC"); + //! [create parser] + + //! [add arguments] + // how should arguments be prefixed + parser.setArgumentPrefix("--", "-"); + // add each argument, unless specified otherwise each argument is optional + // see mitkCommandLineParser::addArgument for more information + parser.beginGroup("Required I/O parameters"); + parser.addArgument( + "input", "i", mitkCommandLineParser::File, "Input file", "Path to the input image that should be splitted. If the image has only one time point it will be stored as output untouched.", us::Any(), false, false, false, mitkCommandLineParser::Input); + parser.addArgument("output", + "o", + mitkCommandLineParser::File, + "Output file(s) path", + "Path to the splitted images. If the input has multiple time points the path will be used as pattern and a suffix \"_[time step]\" will be added before the extension.", + us::Any(), + false, false, false, mitkCommandLineParser::Output); + parser.endGroup(); + + parser.beginGroup("Optional parameters"); + parser.addArgument("help", "h", mitkCommandLineParser::Bool, "Help:", "Show this help text"); + parser.endGroup(); + //! [add arguments] +} + +bool configureApplicationSettings(std::map parsedArgs) +{ + if (parsedArgs.size() == 0) + return false; + + inFileName = us::any_cast(parsedArgs["input"]); + outFileNamePattern = us::any_cast(parsedArgs["output"]); + + return true; +} + +void transferMetaProperties(const mitk::Image* sourceImage, mitk::Image* destinationImage, mitk::TimeStepType ts) +{ + auto props = sourceImage->GetPropertyList()->GetMap(); + + for (const auto& [name, prop] : *props) + { + auto tsProperty = dynamic_cast(prop.GetPointer()); + if (nullptr == tsProperty) + { + destinationImage->SetProperty(name, prop->Clone()); + } + else + { + auto extractedProp = ExtractTimeStepFromTemporoSpatialStringProperty(tsProperty, ts); + destinationImage->SetProperty(name, extractedProp); + } + } +} + +void saveResultImage(const mitk::Image* image, const std::string& fileName, mitk::TimeStepType ts) +{ + std::cout << "time step: "<< ts << " -> " <& parsedArgs = parser.parseArguments(argc, argv); + if (!configureApplicationSettings(parsedArgs)) + { + return EXIT_FAILURE; + }; + + // Show a help message + if (parsedArgs.count("help") || parsedArgs.count("h")) + { + std::cout << parser.helpText(); + return EXIT_SUCCESS; + } + + //! [do processing] + try + { + std::cout << "Load image:" << inFileName << std::endl; + auto image = mitk::IOUtil::Load(inFileName, &readerFilterFunctor); + + std::cout << "Split the image ..." << std::endl; + + if (image->GetTimeGeometry()->CountTimeSteps() == 1) + { + std::cout << "Input contains only one time step; will be passed through" << std::endl; + saveResultImage(image, outFileNamePattern, 0); + } + else + { + std::string extension = itksys::SystemTools::GetFilenameExtension(outFileNamePattern); + std::string filename = itksys::SystemTools::GetFilenameWithoutExtension(outFileNamePattern); + std::string path = itksys::SystemTools::GetFilenamePath(outFileNamePattern); + if (!path.empty()) + { + path = path + mitk::IOUtil::GetDirectorySeparator(); + } + + std::cout << "Process time steps:" << std::endl; + for (mitk::TimeStepType ts = 0; ts < image->GetTimeGeometry()->CountTimeSteps(); ++ts) + { + auto splitImage = mitk::SelectImageByTimeStep(image, ts)->Clone(); + transferMetaProperties(image, splitImage, ts); + + std::string writeName = path + filename + "_" + std::to_string(ts) + extension; + + saveResultImage(splitImage, writeName, ts); + } + std::cout << std::endl; + } + + std::cout << "Processing finished." << std::endl; + + return EXIT_SUCCESS; + } + catch (const std::exception& e) + { + MITK_ERROR << e.what(); + return EXIT_FAILURE; + } + catch (...) + { + MITK_ERROR << "Unexpected error encountered."; + return EXIT_FAILURE; + } +}