/***************************************************************************************** * * * OpenSpace * * * * Copyright (c) 2014-2023 * * * * Permission is hereby granted, free of charge, to any person obtaining a copy of this * * software and associated documentation files (the "Software"), to deal in the Software * * without restriction, including without limitation the rights to use, copy, modify, * * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * * permit persons to whom the Software is furnished to do so, subject to the following * * conditions: * * * * The above copyright notice and this permission notice shall be included in all copies * * or substantial portions of the Software. * * * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * ****************************************************************************************/ #include #include #include #include #include namespace { constexpr std::string_view _loggerCat = "ImageSequencer"; } // namespace namespace openspace { ImageSequencer* ImageSequencer::_instance = nullptr; ImageSequencer& ImageSequencer::ref() { ghoul_assert(_instance != nullptr, "Instance has not been initialized"); return *_instance; } void ImageSequencer::initialize() { ghoul_assert(_instance == nullptr, "Instance already has been initialized"); _instance = new ImageSequencer; _instance->_defaultCaptureImage = absPath("${DATA}/placeholder.png").string(); } void ImageSequencer::deinitialize() { delete _instance; _instance = nullptr; } bool ImageSequencer::isReady() const { return _hasData; } std::pair ImageSequencer::nextTarget(double time) const { const auto it = std::lower_bound( _targetTimes.begin(), _targetTimes.end(), time, [](const std::pair& a, double b) { return a.first < b; } ); if (it != _targetTimes.end() && it != _targetTimes.begin()) { return *it; } else { return { 0.0, "No Target" }; } } std::pair ImageSequencer::currentTarget(double time) const { const auto it = std::lower_bound( _targetTimes.begin(), _targetTimes.end(), time, [](const std::pair& a, double b) { return a.first < b; } ); if (it != _targetTimes.end() && it != _targetTimes.begin()) { return *std::prev(it); } else { return { 0.0, "No Target" }; } } double ImageSequencer::prevCaptureTime(double time) const { const auto it = std::lower_bound( _captureProgression.begin(), _captureProgression.end(), time ); if (it != _captureProgression.end() && it != _captureProgression.begin()) { return *std::prev(it); } else { return 0.0; } } double ImageSequencer::nextCaptureTime(double time) const { const auto it = std::lower_bound( _captureProgression.begin(), _captureProgression.end(), time ); if (it != _captureProgression.end()) { return *it; } else { return 0.0; } } Image ImageSequencer::latestImageForInstrument(const std::string& instrumentID) const { const auto it = _latestImages.find(instrumentID); if (it != _latestImages.end()) { return _latestImages.at(instrumentID); } else { return Image(); } } std::vector> ImageSequencer::activeInstruments(double time) { // first set all instruments to off for (std::pair& i : _switchingMap) { i.second = false; } // go over the filetranslaton map using K = std::string; using V = std::unique_ptr; for (const std::pair& key : _fileTranslation) { // for each spice-instrument for (const std::string& instrumentID : key.second->translations()) { // check if the spice-instrument is active if (isInstrumentActive(time, instrumentID)) { // go over switching map for (std::pair& instrument : _switchingMap) { // if instrument is present in switching map if (instrumentID == instrument.first) { // set as active instrument.second = true; } } } } } // return entire map, seen in GUI return _switchingMap; } bool ImageSequencer::isInstrumentActive(double time, const std::string& instrument) const { for (const std::pair& i : _instrumentTimes) { // check if this instrument is in range if (!i.second.includes(time)) { continue; } // if so, then get the corresponding spiceID const std::vector& is = _fileTranslation.at(i.first)->translations(); // check which specific subinstrument is firing const auto it = std::find(is.begin(), is.end(), instrument); if (it != is.end()) { return true; } } return false; } float ImageSequencer::instrumentActiveTime(double time, const std::string& instrumentID) const { for (const std::pair& i : _instrumentTimes) { // check if this instrument is in range if (!i.second.includes(time)) { continue; } // if so, then get the corresponding spiceID const std::vector& is = _fileTranslation.at(i.first)->translations(); // check which specific subinstrument is firing const auto it = std::find(is.begin(), is.end(), instrumentID); if (it != is.end()) { return static_cast( (time - i.second.start) / (i.second.end - i.second.start) ); } } return -1.f; } std::vector ImageSequencer::imagePaths(const std::string& projectee, const std::string& instrument, double time, double sinceTime) { // TODO: Check how this works with time jumps // check if this instance is either in range or // a valid candidate to recieve data const bool instrumentActive = isInstrumentActive(time, instrument); const bool hasCurrentTime = _subsetMap[projectee]._range.includes(time); const bool hasSinceTime = _subsetMap[projectee]._range.includes(sinceTime); if (!instrumentActive || (!hasCurrentTime && !hasSinceTime)) { return std::vector(); } // for readability we store the iterators auto begin = _subsetMap[projectee]._subset.begin(); auto end = _subsetMap[projectee]._subset.end(); // create temporary storage std::vector captures; // what to look for Image findPrevious, findCurrent; findPrevious.timeRange.start = sinceTime; findCurrent.timeRange.start = time; // find the two iterators that correspond to the latest time jump auto compareTime = [](const Image& a, const Image& b) -> bool { return a.timeRange.start < b.timeRange.start; }; auto curr = std::lower_bound(begin, end, findCurrent , compareTime); auto prev = std::lower_bound(begin, end, findPrevious, compareTime); if (curr == begin || curr == end || prev == begin || prev == end || prev >= curr || curr->timeRange.start < prev->timeRange.start) { return std::vector(); } std::copy_if( prev, curr, back_inserter(captures), [instrument](const Image& i) { return i.activeInstruments[0] == instrument; } ); if (!captures.empty()) { _latestImages[captures.back().activeInstruments.front()] = captures.back(); } std::vector toDelete; for (std::vector::iterator it = captures.begin(); it != captures.end(); ++it) { if (!it->isPlaceholder) { continue; } double beforeDist = std::numeric_limits::max(); if (it != captures.begin()) { beforeDist = std::abs(std::prev(it)->timeRange.start - it->timeRange.start); } double nextDist = std::numeric_limits::max(); if (it != captures.end() - 1) { nextDist = std::abs(std::next(it)->timeRange.start - it->timeRange.start); } if (beforeDist < 1.0 || nextDist < 1.0) { toDelete.push_back(static_cast(std::distance(captures.begin(), it))); } } for (size_t i = 0; i < toDelete.size(); ++i) { // We have to subtract i here as we already have deleted i value before this and // we need to adjust the location int v = toDelete[i] - static_cast(i); captures.erase(captures.begin() + v); } return captures; } void ImageSequencer::sortData() { std::sort( _targetTimes.begin(), _targetTimes.end(), [](const std::pair& a, const std::pair& b) -> bool { return a.first < b.first; } ); std::stable_sort(_captureProgression.begin(), _captureProgression.end()); for (const std::pair& sub : _subsetMap) { std::sort( _subsetMap[sub.first]._subset.begin(), _subsetMap[sub.first]._subset.end(), [](const Image& a, const Image& b) -> bool { return a.timeRange.start < b.timeRange.start; } ); } std::sort( _instrumentTimes.begin(), _instrumentTimes.end(), [](const std::pair& a, const std::pair& b) { return a.second.start < b.second.start; } ); } void ImageSequencer::runSequenceParser(SequenceParser& parser) { std::map>& translations = parser.translations(); std::map& imageData = parser.subsetMap(); const std::vector>& instrumentTimes = parser.instrumentTimes(); const std::vector>& targetTimes = parser.targetTimes(); const std::vector& captureProgression = parser.captureProgression(); // check for sanity if (imageData.empty() || instrumentTimes.empty() || captureProgression.empty()) { LINFO("Parser did not contain images, instrument times or capture progression"); return; } // append data for (std::pair>& it : translations) { _fileTranslation[it.first] = std::move(it.second); } for (std::pair& it : imageData) { if (_subsetMap.find(it.first) == _subsetMap.end()) { // if key not exist yet - add sequence data for key (target) _subsetMap.insert(it); continue; } const std::string& key = it.first; std::vector& source = it.second._subset; // prediction const std::vector& destination = _subsetMap[key]._subset; // imagery // simple search function double min = 10; auto findMin = [&](const std::vector& vec) -> double { for (size_t i = 1; i < vec.size(); ++i) { double e = std::abs(vec[i].timeRange.start - vec[i - 1].timeRange.start); min = std::min(e, min); } return min; }; // find the smallest separation of images in time double epsilon; epsilon = findMin(destination); // set epsilon as 1% smaller than min epsilon -= min * 0.01; // IFF images have same time as mission planned capture, erase that event // from 'predicted event file' (mission-playbook) for (Image& i : source) { for (const Image& j : destination) { const double diff = std::abs(i.timeRange.start - j.timeRange.start); if (diff < epsilon) { source.erase(source.begin() + 1); } } } // pad image data with predictions (ie - where no actual images, add placeholder) _subsetMap[key]._subset.insert( _subsetMap[key]._subset.end(), source.begin(), source.end() ); _subsetMap[key]._range.include(it.second._range); } _instrumentTimes.insert( _instrumentTimes.end(), instrumentTimes.begin(), instrumentTimes.end() ); _targetTimes.insert(_targetTimes.end(), targetTimes.begin(), targetTimes.end()); _captureProgression.insert( _captureProgression.end(), captureProgression.begin(), captureProgression.end() ); // sorting of data _not_ optional sortData(); // extract payload from _fileTranslation for (std::pair>& t : _fileTranslation) { if (t.second->decoderType() == "CAMERA" || t.second->decoderType() == "SCANNER") { const std::vector& spiceIDs = t.second->translations(); for (const std::string& id : spiceIDs) { for (std::pair& switching : _switchingMap) { if (switching.first == id) { switching.second = false; } } } } } _hasData = true; } } // namespace openspace