diff --git a/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/Measurement.h b/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/Measurement.h index 97e52fc99662f9e8330e5a0d17e24605e8e979b0..2bc00e99f566545d5795f8211c1db836817cd5f3 100644 --- a/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/Measurement.h +++ b/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/Measurement.h @@ -33,9 +33,12 @@ namespace CustomInterfaces { class MANTIDQT_CUSTOMINTERFACES_DLL Measurement { public: + typedef const std::string IDType; + /// Constructor - Measurement(const std::string &measurementId, const std::string &subId, - const std::string &label, const std::string &type); + Measurement(const IDType &measurementId, const IDType &subId, + const std::string &label, const std::string &type, + const double angle, const std::string &run); /// Constructional method static Measurement InvalidMeasurement(); @@ -47,18 +50,22 @@ public: ~Measurement(); bool isUseable() const; - std::string id() const; - std::string subId() const; + IDType id() const; + IDType subId() const; + std::string run() const; std::string type() const; std::string label() const; + double angle() const; private: /// Constructor Measurement(); - const std::string m_measurementId; - const std::string m_subId; + const IDType m_measurementId; + const IDType m_subId; const std::string m_label; const std::string m_type; + const double m_angle; + const std::string m_run; bool m_valid; /// Not assignable Measurement &operator=(const Measurement &); diff --git a/MantidQt/CustomInterfaces/src/Measurement.cpp b/MantidQt/CustomInterfaces/src/Measurement.cpp index 0c9f1d54033c4d0ee2cadcfdbe1268af506c1ccb..148fffc8d2f8bb44521cc75ffb6c0d30da64b4a4 100644 --- a/MantidQt/CustomInterfaces/src/Measurement.cpp +++ b/MantidQt/CustomInterfaces/src/Measurement.cpp @@ -9,12 +9,15 @@ namespace CustomInterfaces { * @param subId * @param label * @param type + * @param angle + * @param run */ -Measurement::Measurement(const std::string &measurementId, - const std::string &subId, const std::string &label, - const std::string &type) +Measurement::Measurement(const Measurement::IDType &measurementId, + const Measurement::IDType &subId, + const std::string &label, const std::string &type, + const double angle, const std::string &run) : m_measurementId(measurementId), m_subId(subId), m_label(label), - m_type(type), m_valid(true) { + m_type(type), m_angle(angle), m_run(run), m_valid(true) { if (m_measurementId.empty()) { m_valid = false; @@ -24,13 +27,15 @@ Measurement::Measurement(const std::string &measurementId, m_valid = false; } else if (m_type.empty()) { m_valid = false; + } else if (m_run.empty()) { + m_valid = false; } } /** * Constructor making an invalid Measurement */ -Measurement::Measurement() : m_valid(false) {} +Measurement::Measurement() : m_valid(false), m_angle(0) {} /** * Copy constructor @@ -38,7 +43,8 @@ Measurement::Measurement() : m_valid(false) {} */ Measurement::Measurement(const Measurement &other) : m_measurementId(other.m_measurementId), m_subId(other.m_subId), - m_label(other.m_label), m_type(other.m_type), m_valid(other.m_valid) {} + m_label(other.m_label), m_type(other.m_type), m_angle(other.m_angle), + m_run(other.m_run), m_valid(other.m_valid) {} /// Destructor Measurement::~Measurement() {} @@ -51,13 +57,17 @@ Measurement Measurement::InvalidMeasurement() { return Measurement(); } bool Measurement::isUseable() const { return m_valid; } -std::string Measurement::id() const { return m_measurementId; } +Measurement::IDType Measurement::id() const { return m_measurementId; } -std::string Measurement::subId() const { return m_subId; } +Measurement::IDType Measurement::subId() const { return m_subId; } std::string Measurement::label() const { return m_label; } std::string Measurement::type() const { return m_type; } +double Measurement::angle() const { return m_angle; } + +std::string Measurement::run() const { return m_run; } + } // namespace CustomInterfaces } // namespace Mantid diff --git a/MantidQt/CustomInterfaces/src/ReflMeasureTransferStrategy.cpp b/MantidQt/CustomInterfaces/src/ReflMeasureTransferStrategy.cpp index 31d4c93ddcbda3ea7a1bbc06bc7439613978e2e1..15e655a239eb4543bedfd5aa8acb4e5b7b6afeba 100644 --- a/MantidQt/CustomInterfaces/src/ReflMeasureTransferStrategy.cpp +++ b/MantidQt/CustomInterfaces/src/ReflMeasureTransferStrategy.cpp @@ -1,11 +1,16 @@ #include "MantidQtCustomInterfaces/ReflMeasureTransferStrategy.h" #include "MantidQtCustomInterfaces/ReflMeasurementSource.h" - +#include "MantidQtCustomInterfaces/ReflTableSchema.h" #include "MantidKernel/ICatalogInfo.h" #include "MantidKernel/ProgressBase.h" #include "MantidKernel/UserCatalogInfo.h" #include <boost/regex.hpp> #include <memory> +#include <vector> +#include <map> +#include <utility> +#include <limits> +#include <set> using namespace Mantid::Kernel; @@ -38,26 +43,67 @@ MantidQt::CustomInterfaces::ReflMeasureTransferStrategy::transferRuns( const SearchResultMap &searchResults, Mantid::Kernel::ProgressBase &progress) { + typedef std::vector<Measurement> VecSameMeasurement; + typedef std::map<Measurement::IDType, VecSameMeasurement> + MapGroupedMeasurement; + + MapGroupedMeasurement mapOfMeasurements; for (auto it = searchResults.begin(); it != searchResults.end(); ++it) { const auto location = it->second.location; const auto fuzzyName = it->first; const auto definedPath = m_catInfo->transformArchivePath(location); + // This is where we read the meta data. Measurement metaData = m_measurementSource->obtain(definedPath, fuzzyName); - /* - const Poco::File filePath(loadPath); - if (filePath.exists() && filePath.isFile()) { - - } else { - // Load from this path + // If the measurement information is not consistent, or could not be + // obtained. skip this measurement. + if (metaData.isUseable()) { + if (mapOfMeasurements.find(metaData.id()) == mapOfMeasurements.end()) { + // Start a new group + mapOfMeasurements.insert( + std::make_pair(metaData.id(), VecSameMeasurement(1, metaData))); + } else { + // Add to existing group + mapOfMeasurements[metaData.id()].push_back(metaData); + } } - */ + // Obtaining metadata could take time. progress.report(); } - return std::vector<std::map<std::string, std::string>>(1); // HACK + + // Now flatten everything out into a table-like output + std::vector<std::map<std::string, std::string>> output; + int nextGroupId = 0; + + for (auto group = mapOfMeasurements.begin(); group != mapOfMeasurements.end(); + ++group) { + + // Map keyed by subId to index of exisiting subid written. + std::map<std::string, int> subIdMap; + for (size_t i = 0; i < group->second.size(); ++i) { + const Measurement &measurement = group->second[i]; + if (subIdMap.find(measurement.subId()) != subIdMap.end()) { + // We already have that subid. + const int rowIndex = subIdMap[measurement.subId()]; + std::string currentRuns = output[rowIndex][ReflTableSchema::RUNS]; + output[rowIndex][ReflTableSchema::RUNS] = + currentRuns + "+" + measurement.run(); + } else { + std::map<std::string, std::string> row; + row[ReflTableSchema::RUNS] = measurement.run(); + row[ReflTableSchema::ANGLE] = measurement.angle(); + row[ReflTableSchema::GROUP] = nextGroupId; + subIdMap.insert(std::make_pair(measurement.subId(), i)); + output.push_back(row); + } + } + ++nextGroupId; + } + + return output; } ReflMeasureTransferStrategy *ReflMeasureTransferStrategy::clone() const { diff --git a/MantidQt/CustomInterfaces/test/MeasurementTest.h b/MantidQt/CustomInterfaces/test/MeasurementTest.h index fba52c20ba94bd3046377b372f2f4271ddc7895b..76fe6539e43335ba3cc4d73219f5a263aa6b46c0 100644 --- a/MantidQt/CustomInterfaces/test/MeasurementTest.h +++ b/MantidQt/CustomInterfaces/test/MeasurementTest.h @@ -24,21 +24,25 @@ public: const std::string measurementSubId = "s"; const std::string measurementLabel = "l"; const std::string measurementType = "t"; + const double angle = 0.1; + const std::string run = "123"; Measurement measurement(measurementId, measurementSubId, measurementLabel, - measurementType); + measurementType, angle, run); TS_ASSERT(measurement.isUseable()); TS_ASSERT_EQUALS(measurementId, measurement.id()); TS_ASSERT_EQUALS(measurementSubId, measurement.subId()); TS_ASSERT_EQUALS(measurementLabel, measurement.label()); TS_ASSERT_EQUALS(measurementType, measurement.type()); + TS_ASSERT_EQUALS(angle, measurement.angle()); + TS_ASSERT_EQUALS(run, measurement.run()); } void test_invalid_construction_when_measurementId_empty() { Measurement measurement("", "measurementSubId", "measurementLabel", - "measurementType"); + "measurementType", 0.1, "111"); TS_ASSERT(!measurement.isUseable()); } @@ -46,7 +50,7 @@ public: void test_invalid_construction_when_measurementSubId_empty() { Measurement measurement("measurementId", "", "measurementLabel", - "measurementType"); + "measurementType", 0.1, "111"); TS_ASSERT(!measurement.isUseable()); } @@ -54,14 +58,14 @@ public: void test_invalid_construction_when_label_empty() { Measurement measurement("measurementId", "measurementSubId", "", - "measurementType"); + "measurementType", 0.1, "111"); TS_ASSERT(!measurement.isUseable()); } void test_invalid_construction_when_type_empty() { Measurement measurement("measurementId", "measurementSubId", - "measurementLabel", ""); + "measurementLabel", "", 0.1, "111"); TS_ASSERT(!measurement.isUseable()); } diff --git a/MantidQt/CustomInterfaces/test/ReflMeasureTransferStrategyTest.h b/MantidQt/CustomInterfaces/test/ReflMeasureTransferStrategyTest.h index 71070def522326800f34ea12dd8fccf0dcb3519b..5cb19b55812f197d6063cb60e6d2278d1691f295 100644 --- a/MantidQt/CustomInterfaces/test/ReflMeasureTransferStrategyTest.h +++ b/MantidQt/CustomInterfaces/test/ReflMeasureTransferStrategyTest.h @@ -5,6 +5,7 @@ #include "ReflMainViewMockObjects.h" #include "MantidQtCustomInterfaces/ReflMeasureTransferStrategy.h" #include "MantidQtCustomInterfaces/ReflMeasurementSource.h" +#include "MantidQtCustomInterfaces/ReflTableSchema.h" #include <memory> #include <gmock/gmock.h> #include <utility> @@ -34,16 +35,18 @@ public: void test_obtain_single_measurement() { + // Search result inforation not used in the following since we mock the + // return from the measurementSource SearchResultMap data; - data.insert(std::make_pair<std::string, SearchResult>( - "111", SearchResult("descr", "location"))); + data.insert( + std::make_pair<std::string, SearchResult>("111", SearchResult())); auto mockMeasurementSource = new MockReflMeasurementSource; // We expect that we are going to fetch the measurement data for every // search result. EXPECT_CALL(*mockMeasurementSource, obtain(_, _)) .Times(Exactly(data.size())) - .WillRepeatedly(Return(Measurement("a", "s_a", "l", "t"))); + .WillRepeatedly(Return(Measurement("a", "s_a", "l", "t", 0, "111"))); auto mockCatInfo = new MockICatalogInfo; // We expect that every location will be translated/transformed to make it @@ -53,6 +56,7 @@ public: .WillRepeatedly(Return(std::string())); MockProgressBase progress; + // We expect a progress update on each transfer EXPECT_CALL(progress, doReport(_)).Times(Exactly(data.size())); ReflMeasureTransferStrategy strategy( @@ -66,6 +70,165 @@ public: TS_ASSERT(Mock::VerifyAndClear(mockMeasurementSource)); } + void test_when_two_measurement_ids_match_group_them_but_not_others() { + + // Search result inforation not used in the following since we mock the + // return from the measurementSource + SearchResultMap data; + data.insert( + std::make_pair<std::string, SearchResult>("111", SearchResult())); + data.insert( + std::make_pair<std::string, SearchResult>("112", SearchResult())); + data.insert( + std::make_pair<std::string, SearchResult>("113", SearchResult())); + + auto mockMeasurementSource = new MockReflMeasurementSource; + // We are going to return three SearchResults two have the same measurement + // id + EXPECT_CALL(*mockMeasurementSource, obtain(_, _)) + .Times(Exactly(data.size())) + .WillOnce(Return(Measurement("m1", "s1", "l1", "t1", 0.1, "111"))) + .WillOnce(Return(Measurement("m1", "s2", "l1", "t1", 0.2, "122"))) + .WillOnce(Return(Measurement("m2", "s2", "l1", "t1", 0.2, "123"))); + + auto mockCatInfo = new MockICatalogInfo; + // We expect that every location will be translated/transformed to make it + // os specific + EXPECT_CALL(*mockCatInfo, transformArchivePath(_)) + .Times(Exactly(data.size())) + .WillRepeatedly(Return(std::string())); + + MockProgressBase progress; + // Expect a progress update + EXPECT_CALL(progress, doReport(_)).Times(Exactly(data.size())); + + // Make the transfer stragegy + ReflMeasureTransferStrategy strategy( + std::move(std::unique_ptr<MockICatalogInfo>(mockCatInfo)), + std::move( + std::unique_ptr<MockReflMeasurementSource>(mockMeasurementSource))); + + // Do the transfer + auto transferResult = strategy.transferRuns(data, progress); + + // Check the transfer entries + TSM_ASSERT_EQUALS("Wrong number of rows", 3, transferResult.size()); + + for (size_t i = 1; i < transferResult.size(); ++i) { + TSM_ASSERT_DIFFERS("Runs should be the different for all rows", + transferResult[0][ReflTableSchema::RUNS], + transferResult[i][ReflTableSchema::RUNS]); + } + + TSM_ASSERT_EQUALS("Group should be the same for first two rows", + transferResult[0][ReflTableSchema::GROUP], + transferResult[1][ReflTableSchema::GROUP]); + + TSM_ASSERT_DIFFERS("Group should be different for last rows", + transferResult[0][ReflTableSchema::GROUP], + transferResult[2][ReflTableSchema::GROUP]); + + TS_ASSERT(Mock::VerifyAndClear(mockCatInfo)); + TS_ASSERT(Mock::VerifyAndClear(mockMeasurementSource)); + } + + void test_when_two_measurement_sub_ids_match_combine_rows() { + + // Search result inforation not used in the following since we mock the + // return from the measurementSource + SearchResultMap data; + data.insert( + std::make_pair<std::string, SearchResult>("111", SearchResult())); + data.insert( + std::make_pair<std::string, SearchResult>("112", SearchResult())); + data.insert( + std::make_pair<std::string, SearchResult>("113", SearchResult())); + + auto mockMeasurementSource = new MockReflMeasurementSource; + // All 3 have same measurment id, but we also have 2 with same sub id. + EXPECT_CALL(*mockMeasurementSource, obtain(_, _)) + .Times(Exactly(data.size())) + .WillOnce(Return(Measurement("m1", "s1", "l1", "t1", 0.1, "111"))) + .WillOnce(Return(Measurement("m1", "s1", "l1", "t1", 0.2, "122"))) + .WillOnce(Return(Measurement("m1", "s2", "l1", "t1", 0.2, "123"))); + + auto mockCatInfo = new MockICatalogInfo; + // We expect that every location will be translated/transformed to make it + // os specific + EXPECT_CALL(*mockCatInfo, transformArchivePath(_)) + .Times(Exactly(data.size())) + .WillRepeatedly(Return(std::string())); + + MockProgressBase progress; + // Expect a progress update + EXPECT_CALL(progress, doReport(_)).Times(Exactly(data.size())); + + // Make the transfer stragegy + ReflMeasureTransferStrategy strategy( + std::move(std::unique_ptr<MockICatalogInfo>(mockCatInfo)), + std::move( + std::unique_ptr<MockReflMeasurementSource>(mockMeasurementSource))); + + // Do the transfer + auto transferResult = strategy.transferRuns(data, progress); + + // Check the transfer entries + TSM_ASSERT_EQUALS("Should have two rows", 2, transferResult.size()); + TSM_ASSERT_DIFFERS("Runs should be the different for both columns", + transferResult[0][ReflTableSchema::RUNS], + transferResult[1][ReflTableSchema::RUNS]); + TSM_ASSERT_EQUALS("Runs should be summed. Sub ids are the same.", "111+122", + transferResult[0][ReflTableSchema::RUNS]); + TSM_ASSERT_EQUALS("Other run should be singular.", "123", + transferResult[1][ReflTableSchema::RUNS]); + + for (size_t i = 1; i < transferResult.size(); ++i) { + TSM_ASSERT_EQUALS("All should have the same group", + transferResult[0][ReflTableSchema::GROUP], + transferResult[i][ReflTableSchema::GROUP]); + } + + TS_ASSERT(Mock::VerifyAndClear(mockCatInfo)); + TS_ASSERT(Mock::VerifyAndClear(mockMeasurementSource)); + } + + void test_do_not_include_invalid_measurements() { + // Search result inforation not used in the following since we mock the + // return from the measurementSource + SearchResultMap data; + data.insert( + std::make_pair<std::string, SearchResult>("111", SearchResult())); + + auto mockMeasurementSource = new MockReflMeasurementSource; + // We expect that we are going to fetch the measurement data for every + // search result. + EXPECT_CALL(*mockMeasurementSource, obtain(_, _)) + .Times(Exactly(data.size())) + .WillRepeatedly(Return(Measurement::InvalidMeasurement())); + + auto mockCatInfo = new MockICatalogInfo; + // We expect that every location will be translated/transformed to make it + // os specific + EXPECT_CALL(*mockCatInfo, transformArchivePath(_)) + .Times(Exactly(data.size())); + + MockProgressBase progress; + // Nothing obtained. No progress to report. + EXPECT_CALL(progress, doReport(_)).Times(Exactly(0)); + + ReflMeasureTransferStrategy strategy( + std::move(std::unique_ptr<MockICatalogInfo>(mockCatInfo)), + std::move( + std::unique_ptr<MockReflMeasurementSource>(mockMeasurementSource))); + + auto result = strategy.transferRuns(data, progress); + + TSM_ASSERT_EQUALS("Measurements where invalid. Results should be empty.", 0, + result.size()); + TS_ASSERT(Mock::VerifyAndClear(mockCatInfo)); + TS_ASSERT(Mock::VerifyAndClear(mockMeasurementSource)); + } + void test_clone() { // Sub component ICatalogInfo will be cloned