satdump/src-core/core/pipeline.cpp

532 lines
22 KiB
C++
Raw Permalink Normal View History

2021-03-11 19:28:50 +01:00
#define SATDUMP_DLL_EXPORT 1
2021-02-14 12:05:44 +01:00
#include "pipeline.h"
#include "logger.h"
2021-02-21 15:11:02 +01:00
#include <fstream>
2021-04-02 14:48:28 +02:00
#include <filesystem>
#include <thread>
2022-05-18 20:31:21 +02:00
#include "core/config.h"
2024-03-14 12:12:34 +01:00
#include "core/exception.h"
2024-07-10 11:22:21 -04:00
#include "init.h"
#include "nlohmann/json_utils.h"
2021-02-21 15:11:02 +01:00
2022-03-10 19:56:37 +01:00
namespace satdump
2021-02-14 12:05:44 +01:00
{
2022-03-10 19:56:37 +01:00
SATDUMP_DLL std::vector<Pipeline> pipelines;
2024-07-10 15:27:49 -04:00
SATDUMP_DLL nlohmann::ordered_json pipelines_json;
2024-07-20 00:17:57 -04:00
SATDUMP_DLL nlohmann::ordered_json pipelines_system_json;
2024-07-13 10:09:55 -04:00
std::string user_cfg_path;
2022-03-10 19:56:37 +01:00
void Pipeline::run(std::string input_file,
std::string output_directory,
nlohmann::json parameters,
std::string input_level,
bool ui,
std::shared_ptr<std::vector<std::shared_ptr<ProcessingModule>>> uiCallList,
std::shared_ptr<std::mutex> uiCallListMutex)
2021-06-08 16:33:20 +02:00
{
2022-03-10 19:56:37 +01:00
if (!std::filesystem::exists(input_file))
{
logger->error("Input file " + input_file + " does not exist!");
return;
}
2021-06-08 16:33:20 +02:00
2022-03-10 19:56:37 +01:00
logger->debug("Starting " + name);
2021-02-14 12:05:44 +01:00
2022-03-10 19:56:37 +01:00
std::vector<std::string> lastFiles;
2021-02-14 12:05:44 +01:00
2022-03-10 19:56:37 +01:00
int currentStep = 0;
int stepC = 0;
bool foundLevel = false;
2021-02-14 12:05:44 +01:00
2022-03-10 19:56:37 +01:00
/*
In most cases, all processing pipelines will start from
baseband, demodulate to some intermediate level and then
feed an actual decoder down to frames.
2022-03-10 19:56:37 +01:00
Hence, in most cases, if the first and second module both
support streaming data from the first to the second, we
can skip this intermediate level and do both in parrallel.
2022-03-10 19:56:37 +01:00
Here, we first test modules are compatible with this way
of doing things, then unless specifically disabled by the
2024-07-19 23:02:13 -04:00
user, proceed to run both in parallel saving up on processing
2022-03-10 19:56:37 +01:00
time.
*/
2024-07-01 17:00:20 -04:00
if (input_level == "baseband" &&
parameters.count("disable_multi_modules") == 0 &&
steps[1].modules.size() == 1 &&
steps[2].modules.size() == 1)
2022-03-10 19:56:37 +01:00
{
logger->info("Checking the 2 first modules...");
2022-03-10 19:56:37 +01:00
PipelineModule module1 = steps[1].modules[0];
PipelineModule module2 = steps[2].modules[0];
2022-03-10 19:56:37 +01:00
if (modules_registry.count(module1.module_name) <= 0 || modules_registry.count(module2.module_name) <= 0)
2024-03-14 12:12:34 +01:00
throw satdump_exception("Module " + module1.module_name + " or " + module2.module_name + " is not registered. Cancelling pipeline.");
2022-03-10 19:56:37 +01:00
nlohmann::json params1 = prepareParameters(module1.parameters, parameters);
nlohmann::json params2 = prepareParameters(module2.parameters, parameters);
2022-03-10 19:56:37 +01:00
std::shared_ptr<ProcessingModule> m1 = modules_registry[module1.module_name](module1.input_override == "" ? input_file : output_directory + "/" + module1.input_override,
output_directory + "/" + name,
params1);
std::shared_ptr<ProcessingModule> m2 = modules_registry[module2.module_name](module2.input_override == "" ? input_file : output_directory + "/" + module2.input_override,
output_directory + "/" + name,
params2);
2022-03-10 19:56:37 +01:00
std::vector<ModuleDataType> m1_outputs = m1->getOutputTypes();
std::vector<ModuleDataType> m2_inputs = m2->getInputTypes();
2022-03-10 19:56:37 +01:00
bool m1_has_stream = std::find(m1_outputs.begin(), m1_outputs.end(), DATA_STREAM) != m1_outputs.end();
bool m2_has_stream = std::find(m2_inputs.begin(), m2_inputs.end(), DATA_STREAM) != m2_inputs.end();
2022-03-10 19:56:37 +01:00
if (m1_has_stream && m2_has_stream)
{
logger->info("Both 2 first modules can be run at once!");
2022-03-10 19:56:37 +01:00
m1->setInputType(DATA_FILE);
m1->setOutputType(DATA_STREAM);
m1->output_fifo = std::make_shared<dsp::RingBuffer<uint8_t>>(1000000);
2022-03-10 19:56:37 +01:00
m2->input_fifo = m1->output_fifo;
m2->setInputType(DATA_STREAM);
m2->setOutputType(DATA_FILE);
m2->input_active = true;
2022-03-10 19:56:37 +01:00
m1->init();
m2->init();
2022-03-10 19:56:37 +01:00
if (ui)
{
uiCallListMutex->lock();
uiCallList->push_back(m1);
uiCallList->push_back(m2);
uiCallListMutex->unlock();
}
2024-01-28 21:10:09 +01:00
std::thread module1_thread([&m1]()
2023-09-21 16:54:50 +02:00
{ m1->process(); });
2024-01-28 21:10:09 +01:00
std::thread module2_thread([&m2]()
2023-09-21 16:54:50 +02:00
{ m2->process(); });
2022-03-10 19:56:37 +01:00
if (module1_thread.joinable())
module1_thread.join();
while (m2->input_fifo->getReadable() > 0)
std::this_thread::sleep_for(std::chrono::seconds(1));
2022-03-10 19:56:37 +01:00
m2->input_active = false;
m2->input_fifo->stopReader();
m2->input_fifo->stopWriter();
m2->stop();
if (module2_thread.joinable())
module2_thread.join();
2022-03-10 19:56:37 +01:00
if (ui)
{
uiCallListMutex->lock();
uiCallList->clear();
uiCallListMutex->unlock();
}
2022-03-10 19:56:37 +01:00
lastFiles = m2->getOutputs();
currentStep += 2;
input_level = steps[2].level_name;
stepC++;
}
}
2022-03-10 19:56:37 +01:00
for (; currentStep < (int)steps.size(); currentStep++)
2021-02-14 12:05:44 +01:00
{
2022-03-10 19:56:37 +01:00
PipelineStep &step = steps[currentStep];
2021-02-14 12:05:44 +01:00
2022-03-10 19:56:37 +01:00
if (!foundLevel)
{
foundLevel = step.level_name == input_level;
2023-05-20 22:40:36 +02:00
logger->info("Data is already at level " + step.level_name + ", skipping");
2022-03-10 19:56:37 +01:00
continue;
}
2021-02-14 12:05:44 +01:00
2023-05-20 22:40:36 +02:00
logger->info("Processing data to level " + step.level_name);
2022-03-10 19:56:37 +01:00
std::vector<std::string> files;
2022-03-10 19:56:37 +01:00
for (PipelineModule modStep : step.modules)
{
// Check module exists!
if (modules_registry.count(modStep.module_name) <= 0)
2024-03-14 12:12:34 +01:00
throw satdump_exception("Module " + modStep.module_name + " is not registered. Cancelling pipeline.");
2021-02-17 20:07:49 +01:00
2022-03-10 19:56:37 +01:00
nlohmann::json final_parameters = prepareParameters(modStep.parameters, parameters);
2021-02-20 00:36:44 +01:00
2022-03-10 19:56:37 +01:00
std::shared_ptr<ProcessingModule> module = modules_registry[modStep.module_name](modStep.input_override == "" ? (stepC == 0 ? input_file : lastFiles[0]) : output_directory + "/" + modStep.input_override,
output_directory + "/" + name,
final_parameters);
2021-03-22 22:26:21 +01:00
2022-03-10 19:56:37 +01:00
module->setInputType(DATA_FILE);
module->setOutputType(DATA_FILE);
2021-02-17 20:07:49 +01:00
2022-03-10 19:56:37 +01:00
module->init();
2021-02-17 20:07:49 +01:00
2022-03-10 19:56:37 +01:00
if (ui)
{
uiCallListMutex->lock();
uiCallList->push_back(module);
uiCallListMutex->unlock();
}
2021-02-17 20:07:49 +01:00
2022-03-10 19:56:37 +01:00
module->process();
2022-02-25 16:29:35 +01:00
2022-03-10 19:56:37 +01:00
if (ui)
{
uiCallListMutex->lock();
uiCallList->clear();
uiCallListMutex->unlock();
}
2021-02-17 20:07:49 +01:00
2022-03-10 19:56:37 +01:00
std::vector<std::string> newfiles = module->getOutputs();
files.insert(files.end(), newfiles.begin(), newfiles.end());
2021-02-21 15:11:02 +01:00
2022-03-10 19:56:37 +01:00
module.reset();
}
2022-03-10 19:56:37 +01:00
lastFiles = files;
stepC++;
}
2022-05-18 20:31:21 +02:00
// We are done. Does this have a dataset?
bool input_is_dataset = std::filesystem::path(input_file).stem().string() == "dataset" && std::filesystem::path(input_file).extension().string() == ".json";
if ((std::filesystem::exists(output_directory + "/dataset.json") || input_is_dataset) &&
2022-05-18 20:31:21 +02:00
config::main_cfg["satdump_general"]["auto_process_products"]["value"].get<bool>())
{
logger->debug("Products processing is enabled! Starting processing module.");
std::string dataset_path = output_directory + "/dataset.json";
if (input_is_dataset)
dataset_path = input_file;
2022-05-18 20:31:21 +02:00
// It does, fire up the processing module.
std::shared_ptr<ProcessingModule> module = modules_registry["products_processor"](dataset_path,
2022-05-18 20:31:21 +02:00
output_directory + "/" + name,
"");
module->setInputType(DATA_FILE);
module->setOutputType(DATA_FILE);
module->init();
if (ui)
{
uiCallListMutex->lock();
uiCallList->push_back(module);
uiCallListMutex->unlock();
}
module->process();
if (ui)
{
uiCallListMutex->lock();
uiCallList->clear();
uiCallListMutex->unlock();
}
module.reset();
}
2023-09-21 16:54:50 +02:00
satdump::eventBus->fire_event<events::PipelineDoneProcessingEvent>({name, output_directory});
2022-03-10 19:56:37 +01:00
}
2022-03-10 19:56:37 +01:00
nlohmann::json Pipeline::prepareParameters(nlohmann::json &module_params, nlohmann::json &pipeline_params)
{
nlohmann::json final_parameters = module_params;
for (const nlohmann::detail::iteration_proxy_value<nlohmann::detail::iter_impl<nlohmann::json>> &param : pipeline_params.items())
if (final_parameters.count(param.key()) > 0)
2022-03-25 19:33:31 +01:00
final_parameters[param.key()] = param.value();
2022-03-10 19:56:37 +01:00
else
final_parameters.emplace(param.key(), param.value());
logger->debug("Parameters :");
for (const nlohmann::detail::iteration_proxy_value<nlohmann::detail::iter_impl<nlohmann::json>> &param : final_parameters.items())
logger->debug(" - " + param.key() + " : " + param.value().dump());
return final_parameters;
}
2022-05-12 18:19:01 +02:00
void loadPipeline(std::string filepath)
2022-03-10 19:56:37 +01:00
{
logger->info("Loading pipelines from file " + filepath);
2021-02-21 15:11:02 +01:00
2022-03-10 19:56:37 +01:00
// Read file into a string
std::ifstream fileStream(filepath);
std::string pipelineString((std::istreambuf_iterator<char>(fileStream)),
(std::istreambuf_iterator<char>()));
fileStream.close();
2022-03-10 19:56:37 +01:00
// Replace "includes"
{
2022-03-10 19:56:37 +01:00
std::map<std::string, std::string> toReplace;
for (int i = 0; i < int(pipelineString.size() - sizeof(".json.inc")); i++)
{
2022-03-10 19:56:37 +01:00
std::string currentPos = pipelineString.substr(i, 9);
if (currentPos == ".json.inc")
{
2022-03-10 19:56:37 +01:00
int bracketPos = i;
for (int y = i; y >= 0; y--)
{
2022-03-10 19:56:37 +01:00
if (pipelineString[y] == '"')
{
bracketPos = y;
break;
}
}
2022-03-10 19:56:37 +01:00
std::string finalStr = pipelineString.substr(bracketPos, (i - bracketPos) + 10);
std::string filenameToLoad = finalStr.substr(1, finalStr.size() - 2);
std::string pathToLoad = std::filesystem::path(filepath).parent_path().string() + "/" + filenameToLoad;
2022-03-10 19:56:37 +01:00
if (std::filesystem::exists(pathToLoad))
{
std::ifstream fileStream(pathToLoad);
std::string includeString((std::istreambuf_iterator<char>(fileStream)),
(std::istreambuf_iterator<char>()));
fileStream.close();
2022-03-10 19:56:37 +01:00
toReplace.emplace(finalStr, includeString);
}
else
{
logger->error("Could not include " + pathToLoad + "!");
}
}
}
2022-03-10 19:56:37 +01:00
for (std::pair<std::string, std::string> replace : toReplace)
{
while (pipelineString.find(replace.first) != std::string::npos)
pipelineString.replace(pipelineString.find(replace.first), replace.first.size(), replace.second);
}
2022-03-10 19:56:37 +01:00
// logger->info(pipelineString);
}
2024-07-10 11:22:21 -04:00
try
{
2024-07-21 09:09:37 -04:00
pipelines_system_json.update(nlohmann::ordered_json::parse(pipelineString));
2024-07-10 11:22:21 -04:00
}
catch (std::exception &e)
{
logger->warn("Error loading system pipeline file: %s", e.what());
}
}
2021-02-21 15:11:02 +01:00
2024-07-10 11:22:21 -04:00
void parsePipelines()
{
pipelines.clear();
2024-07-10 11:22:21 -04:00
for (nlohmann::detail::iteration_proxy_value<nlohmann::detail::iter_impl<nlohmann::ordered_json>> pipelineConfig : pipelines_json.items())
2021-02-21 15:11:02 +01:00
{
2022-03-10 19:56:37 +01:00
Pipeline newPipeline;
2022-06-10 19:25:58 +02:00
// Parse basics
2022-03-10 19:56:37 +01:00
newPipeline.name = pipelineConfig.key();
newPipeline.readable_name = pipelineConfig.value()["name"];
2022-03-10 20:50:47 +01:00
newPipeline.editable_parameters = pipelineConfig.value()["parameters"];
2022-06-10 19:25:58 +02:00
// Parse live configuration if preset
newPipeline.live = pipelineConfig.value().contains("live") ? pipelineConfig.value()["live"].get<bool>() : false;
2022-03-10 19:56:37 +01:00
if (newPipeline.live)
{
try
{
newPipeline.live_cfg.normal_live = pipelineConfig.value()["live_cfg"].get<std::vector<std::pair<int, int>>>();
}
2023-09-21 16:54:50 +02:00
catch (std::exception &)
{
newPipeline.live_cfg.normal_live = pipelineConfig.value()["live_cfg"]["default"].get<std::vector<std::pair<int, int>>>();
if (pipelineConfig.value()["live_cfg"].contains("server"))
newPipeline.live_cfg.server_live = pipelineConfig.value()["live_cfg"]["server"].get<std::vector<std::pair<int, int>>>();
if (pipelineConfig.value()["live_cfg"].contains("client"))
2022-07-04 16:33:09 +02:00
newPipeline.live_cfg.client_live = pipelineConfig.value()["live_cfg"]["client"].get<std::vector<std::pair<int, int>>>();
if (pipelineConfig.value()["live_cfg"].contains("pkt_size"))
newPipeline.live_cfg.pkt_size = pipelineConfig.value()["live_cfg"]["pkt_size"].get<int>();
}
}
2022-06-10 19:25:58 +02:00
// Parse and set presets
if (newPipeline.editable_parameters.contains("samplerate"))
{ // We attempt to get a preset samplerate
if (newPipeline.editable_parameters["samplerate"].contains("value"))
newPipeline.preset.samplerate = newPipeline.editable_parameters["samplerate"]["value"];
else
newPipeline.preset.samplerate = 0;
}
if (pipelineConfig.value().contains("frequencies"))
newPipeline.preset.frequencies = pipelineConfig.value()["frequencies"].get<std::vector<std::pair<std::string, uint64_t>>>();
2022-03-10 20:50:47 +01:00
2022-03-10 19:56:37 +01:00
// logger->info(newPipeline.name);
bool hasAllModules = true;
for (nlohmann::detail::iteration_proxy_value<nlohmann::detail::iter_impl<nlohmann::ordered_json>> pipelineStep : pipelineConfig.value()["work"].items())
2021-02-21 15:11:02 +01:00
{
Pipeline::PipelineStep newStep;
2022-03-10 19:56:37 +01:00
newStep.level_name = pipelineStep.key();
// logger->warn(newStep.level_name);
2022-02-21 23:32:11 +01:00
2022-03-10 19:56:37 +01:00
for (nlohmann::detail::iteration_proxy_value<nlohmann::detail::iter_impl<nlohmann::ordered_json>> pipelineModule : pipelineStep.value().items())
2022-02-21 23:32:11 +01:00
{
Pipeline::PipelineModule newModule;
2022-03-10 19:56:37 +01:00
newModule.module_name = pipelineModule.key();
newModule.parameters = pipelineModule.value();
if (newModule.parameters.count("input_override") > 0)
newModule.input_override = newModule.parameters["input_override"];
else
newModule.input_override = "";
// logger->debug(newModule.module_name);
if (modules_registry.count(newModule.module_name) <= 0 && hasAllModules)
{
logger->warn("Module " + newModule.module_name + " is not loaded. Skipping pipeline!");
hasAllModules = false;
}
newStep.modules.push_back(newModule);
2022-02-21 23:32:11 +01:00
}
2021-02-21 15:11:02 +01:00
2022-03-10 19:56:37 +01:00
newPipeline.steps.push_back(newStep);
2021-02-21 15:11:02 +01:00
}
2022-03-10 19:56:37 +01:00
if (hasAllModules)
pipelines.push_back(newPipeline);
2021-02-21 15:11:02 +01:00
}
std::sort(pipelines.begin(), pipelines.end(), [](const Pipeline &l, const Pipeline &r)
{
std::string lname = l.readable_name;
std::string rname = r.readable_name;
std::transform(lname.begin(), lname.end(), lname.begin(), ::tolower);
std::transform(rname.begin(), rname.end(), rname.begin(), ::tolower);
return lname < rname; });
2021-02-21 15:11:02 +01:00
}
2021-04-02 14:48:28 +02:00
void loadPipelines(std::string filepath)
2022-03-10 19:56:37 +01:00
{
2023-11-26 18:21:53 +01:00
if (!std::filesystem::exists(filepath))
{
logger->error("Couldn't load pipelines! Was trying : " + filepath);
exit(1);
}
2024-07-10 11:22:21 -04:00
logger->info("Loading system pipelines from " + filepath);
2021-04-02 14:48:28 +02:00
2024-07-10 11:22:21 -04:00
std::vector<std::string> systemPipelines;
2022-03-10 19:56:37 +01:00
std::filesystem::recursive_directory_iterator pipelinesIterator(filepath);
std::error_code iteratorError;
while (pipelinesIterator != std::filesystem::recursive_directory_iterator())
2021-04-02 14:48:28 +02:00
{
2022-03-10 19:56:37 +01:00
if (!std::filesystem::is_directory(pipelinesIterator->path()))
2021-04-02 14:48:28 +02:00
{
2022-03-10 19:56:37 +01:00
if (pipelinesIterator->path().filename().string().find(".json") != std::string::npos)
{
2022-03-10 19:56:37 +01:00
if (pipelinesIterator->path().string().find(".json.inc") == std::string::npos)
{
2024-07-10 11:22:21 -04:00
logger->trace("Found system pipeline file " + pipelinesIterator->path().string());
systemPipelines.push_back(pipelinesIterator->path().string());
2022-03-10 19:56:37 +01:00
}
}
2021-04-02 14:48:28 +02:00
}
2022-03-10 19:56:37 +01:00
pipelinesIterator.increment(iteratorError);
if (iteratorError)
logger->critical(iteratorError.message());
2021-04-02 14:48:28 +02:00
}
std::sort(systemPipelines.begin(), systemPipelines.end());
2024-07-10 11:22:21 -04:00
for (std::string &pipeline : systemPipelines)
loadPipeline(pipeline);
// Add User Pipelines
nlohmann::ordered_json user_pipelines;
bool has_user_pipelines = false;
2024-07-13 10:09:55 -04:00
std::string final_path = "";
if (std::filesystem::exists("pipelines.json")) // First try loading in current folder
final_path = "pipelines.json";
else if (std::filesystem::exists(user_path + "/pipelines.json"))
final_path = user_path + "/pipelines.json";
if (final_path != "")
2024-07-10 11:22:21 -04:00
{
2024-07-13 10:09:55 -04:00
logger->info("Found user pipelines " + final_path);
user_cfg_path = final_path;
2024-07-10 11:22:21 -04:00
has_user_pipelines = true;
try
{
2024-07-19 23:02:13 -04:00
pipelines_json = merge_json_diffs(pipelines_system_json, loadJsonFile(user_cfg_path));
2024-07-10 11:22:21 -04:00
}
catch (std::exception &e)
2024-07-10 11:22:21 -04:00
{
logger->warn("Error loading user pipelines: %s", e.what());
has_user_pipelines = false;
}
}
2024-07-13 10:09:55 -04:00
else
{
user_cfg_path = user_path + "/pipelines.json";
}
2024-07-10 11:22:21 -04:00
if (!has_user_pipelines)
pipelines_json = pipelines_system_json;
2024-07-10 11:22:21 -04:00
parsePipelines();
2021-04-02 14:48:28 +02:00
}
2024-07-10 15:27:49 -04:00
void savePipelines()
{
2024-07-19 23:02:13 -04:00
// Check edited pipelines are valid
try
{
parsePipelines();
}
catch (std::exception &e)
{
logger->error("Error parsing customized pipelines! Resetting to last good config\n\n%s", e.what());
pipelines_json = merge_json_diffs(pipelines_system_json, loadJsonFile(user_cfg_path));
parsePipelines();
}
// Save Pipelines
2024-07-10 15:27:49 -04:00
nlohmann::ordered_json diff_json = perform_json_diff(pipelines_system_json, pipelines_json);
2024-07-13 10:09:55 -04:00
try
{
if (!std::filesystem::exists(std::filesystem::path(user_cfg_path).parent_path()) &&
std::filesystem::path(user_cfg_path).has_parent_path())
std::filesystem::create_directories(std::filesystem::path(user_cfg_path).parent_path());
}
catch (std::exception &e)
2024-07-13 10:09:55 -04:00
{
logger->error("Cannot create directory for user pipelines: %s", e.what());
return;
}
logger->info("Saving user pipelines at " + user_cfg_path);
saveJsonFile(user_cfg_path, diff_json);
}
2022-03-10 19:56:37 +01:00
std::optional<Pipeline> getPipelineFromName(std::string downlink_pipeline)
2021-04-02 14:48:28 +02:00
{
2022-03-10 19:56:37 +01:00
std::vector<Pipeline>::iterator it = std::find_if(pipelines.begin(),
pipelines.end(),
[&downlink_pipeline](const Pipeline &e)
{
return e.name == downlink_pipeline;
});
if (it != pipelines.end())
return std::optional<Pipeline>(*it);
else
return std::optional<Pipeline>();
2021-04-02 14:48:28 +02:00
}
}