From a74714b42793f0871d2376f24526e19b31ae7500 Mon Sep 17 00:00:00 2001 From: Joachim Danmayr <46405460+joda01@users.noreply.github.com> Date: Fri, 11 Oct 2024 17:18:30 +0200 Subject: [PATCH] feat: Performance improvements on project load (#82) * chore: Excahneg save favorite symbols * chore: Speed up pipeline loading * feat: Thread for image preparation * chore: Store image version to settings * chore: Store settingd * chore: Added some more nullptr check * chore: Smaller fixes * chore: Fixed linux java path * chore: Another try to fix macos build * chore: Tried to fix arm build * chore: Further buld fix * chore: Store version in database --- .github/workflows/cmake-multi-platform.yml | 9 +- README.md | 8 + src/backend/helper/base64.hpp | 71 +++++++ src/backend/helper/database/database.cpp | 177 +++++++++++------- src/backend/helper/database/database.hpp | 10 +- .../helper/database/exporter/exporter.cpp | 163 +++++++++++----- .../helper/database/exporter/exporter.hpp | 16 +- .../database/plugins/stats_for_image.cpp | 28 +-- .../helper/duration_count/duration_count.cpp | 2 - src/backend/helper/helper.hpp | 16 ++ src/backend/helper/reader/image_reader.cpp | 16 +- src/backend/processor/processor.cpp | 2 +- src/backend/settings/analze_settings.hpp | 6 +- src/backend/settings/program/program_meta.hpp | 34 ++++ .../project_settings/experiment_settings.hpp | 2 +- src/backend/settings/settings.cpp | 10 +- src/backend/settings/settings.hpp | 4 +- .../container/pipeline/add_command_button.cpp | 8 +- .../container/pipeline/add_command_button.hpp | 2 +- .../pipeline/panel_pipeline_settings.cpp | 2 +- src/ui/results/dialog_export_data.cpp | 23 ++- src/ui/results/dialog_export_data.hpp | 3 +- src/ui/results/panel_results.cpp | 14 +- 23 files changed, 445 insertions(+), 181 deletions(-) create mode 100644 src/backend/helper/base64.hpp create mode 100644 src/backend/settings/program/program_meta.hpp diff --git a/.github/workflows/cmake-multi-platform.yml b/.github/workflows/cmake-multi-platform.yml index daa9fae5..780052c6 100644 --- a/.github/workflows/cmake-multi-platform.yml +++ b/.github/workflows/cmake-multi-platform.yml @@ -148,15 +148,15 @@ jobs: cd output mkdir -p ./plugins mkdir -p ./models - mkdir -p ./lib - mkdir -p ./java cp -r ../imagec.app imagec.app cp -r /Users/runner/.conan2/p/b/*/p/./plugins/* ./plugins cp -r ${GITHUB_WORKSPACE}/resources/templates ./templates strip ./imagec.app/Contents/MacOS/imagec chmod +x ./imagec.app/Contents/MacOS/imagec chmod +x ./imagec.app - cd lib + mkdir -p imagec.app/Contents/MacOS/lib + mkdir -p imagec.app/Contents/MacOS/java + cd imagec.app/Contents/MacOS/lib cp /Users/runner/.conan2/p/b/*/p/lib/libQt6Core.6.dylib . cp /Users/runner/.conan2/p/b/*/p/lib/libQt6Gui.6.dylib . cp /Users/runner/.conan2/p/b/*/p/lib/libQt6Widgets.6.dylib . @@ -165,10 +165,11 @@ jobs: cd java cp ${GITHUB_WORKSPACE}/resources/java/bioformats.jar . cp ${GITHUB_WORKSPACE}/resources/java/BioFormatsWrapper.class . + mkdir -p jre_macos_arm + cd jre_macos_arm cp -r ${GITHUB_WORKSPACE}/resources/java/jre_macos_arm.zip . unzip jre_macos_arm.zip rm -rf jre_macos_arm.zip - cd .. - name: Sign Binaries diff --git a/README.md b/README.md index ed57ee79..96e89385 100644 --- a/README.md +++ b/README.md @@ -151,3 +151,11 @@ microsoft/onnxruntime |https://github.com/microsoft/onnxruntime | MIT Thank's to the authors of [imagej](https://github.com/imagej/imagej2) I ported some image processing algorithms from to C++. imageC is the follower of [evanalyzer](https://github.com/joda01/evanalyzer). + + + +## For MacOS you have to do + +chmod +x imagec.app/Contents/MacOS/imagec +xattr -dr com.apple.quarantine imagec.app +open imagec.app diff --git a/src/backend/helper/base64.hpp b/src/backend/helper/base64.hpp new file mode 100644 index 00000000..a4d00b1c --- /dev/null +++ b/src/backend/helper/base64.hpp @@ -0,0 +1,71 @@ +/// +/// \file base64.hpp +/// \author Joachim Danmayr +/// \date 2024-10-11 +/// +/// \copyright Copyright 2019 Joachim Danmayr +/// All rights reserved! This file is subject +/// to the terms and conditions defined in file +/// LICENSE.txt, which is part of this package. +/// +/// + +#pragma once + +#include +#include + +namespace joda::helper { + +static const std::string base64_chars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + +inline std::string base64Encode(const std::string &in) +{ + std::string out; + int val = 0; + int valb = -6; + for(unsigned char c : in) { + val = (val << 8) + c; + valb += 8; + while(valb >= 0) { + out.push_back(base64_chars[(val >> valb) & 0x3F]); + valb -= 6; + } + } + if(valb > -6) { + out.push_back(base64_chars[((val << 8) >> (valb + 8)) & 0x3F]); + } + while(out.size() % 4) { + out.push_back('='); + } + return out; +} + +inline std::string base64Decode(const std::string &in) +{ + std::string out; + std::vector T(256, -1); + for(int i = 0; i < 64; i++) { + T[base64_chars[i]] = i; + } + + int val = 0; + int valb = -8; + for(unsigned char c : in) { + if(T[c] == -1) { + break; // Ignore non-base64 characters + } + val = (val << 6) + T[c]; + valb += 6; + if(valb >= 0) { + out.push_back(char((val >> valb) & 0xFF)); + valb -= 8; + } + } + return out; +} + +} // namespace joda::helper diff --git a/src/backend/helper/database/database.cpp b/src/backend/helper/database/database.cpp index 7d4141b0..218784da 100644 --- a/src/backend/helper/database/database.cpp +++ b/src/backend/helper/database/database.cpp @@ -14,12 +14,14 @@ #include #include #include +#include #include #include #include "backend/artifacts/object_list/object_list.hpp" #include "backend/enums/enums_classes.hpp" #include "backend/enums/enums_clusters.hpp" #include "backend/enums/enums_grouping.hpp" +#include "backend/helper/base64.hpp" #include "backend/helper/duration_count/duration_count.h" #include "backend/helper/file_grouper/file_grouper.hpp" #include "backend/helper/file_grouper/file_grouper_types.hpp" @@ -27,11 +29,13 @@ #include "backend/helper/logger/console_logger.hpp" #include "backend/helper/reader/image_reader.hpp" #include "backend/helper/rle/rle.hpp" +#include "backend/helper/threadpool/thread_pool.hpp" #include "backend/helper/uuid.hpp" #include "backend/processor/initializer/pipeline_initializer.hpp" #include "backend/settings/analze_settings.hpp" #include "backend/settings/project_settings/project_class.hpp" #include "backend/settings/project_settings/project_plates.hpp" +#include "backend/settings/settings.hpp" #include #include #include @@ -65,6 +69,7 @@ void Database::createTables() " experiment_id UUID," " job_id UUID," " job_name TEXT, " + " imagec_version TEXT, " " time_started TIMESTAMP," " time_finished TIMESTAMP," " settings TEXT," @@ -306,7 +311,7 @@ void Database::insertObjects(const joda::processor::ImageContext &imgContext, co } auto Database::prepareImages(uint8_t plateId, enums::GroupBy groupBy, const std::string &filenameRegex, - const std::vector &imagePaths) + const std::vector &imagePaths, BS::thread_pool &globalThreadPool) -> std::vector> { std::vector> imagesToProcess; @@ -318,69 +323,84 @@ auto Database::prepareImages(uint8_t plateId, enums::GroupBy groupBy, const std: auto images_groups = duckdb::Appender(*connection, "images_groups"); auto images_channels = duckdb::Appender(*connection, "images_channels"); std::set addedGroups; + + std::mutex insertMutex; + // // Preparing -> Insert all images to database // + BS::multi_future prepareFuture; + for(const auto &imagePath : imagePaths) { - auto ome = joda::image::reader::ImageReader::getOmeInformation(imagePath); - uint64_t imageId = joda::helper::fnv1a(imagePath.string()); - - imagesToProcess.emplace_back(imagePath, ome, imageId); - auto groupInfo = grouper.getGroupForFilename(imagePath); - // Group - { - if(!addedGroups.contains(groupInfo.groupId)) { - groups.BeginRow(); - groups.Append(plateId); // " plate_id USMALLINT," - groups.Append(groupInfo.groupId); // " group_id USMALLINT," - groups.Append(groupInfo.groupName); // " name STRING," - groups.Append(""); // " notes STRING," - groups.Append(groupInfo.wellPosX); // " pos_on_plate_x UINTEGER," - groups.Append(groupInfo.wellPosY); // " pos_on_plate_y UINTEGER," - groups.EndRow(); - addedGroups.emplace(groupInfo.groupId); + auto prepareImage = [&groups, &grouper, &addedGroups, &imagesToProcess, &images, &images_groups, &images_channels, &insertMutex, plateId, + imagePath]() { + auto ome = joda::image::reader::ImageReader::getOmeInformation(imagePath); + uint64_t imageId = joda::helper::fnv1a(imagePath.string()); + auto groupInfo = grouper.getGroupForFilename(imagePath); + + { + std::lock_guard lock(insertMutex); + imagesToProcess.emplace_back(imagePath, ome, imageId); + // Group + { + if(!addedGroups.contains(groupInfo.groupId)) { + groups.BeginRow(); + groups.Append(plateId); // " plate_id USMALLINT," + groups.Append(groupInfo.groupId); // " group_id USMALLINT," + groups.Append(groupInfo.groupName); // " name STRING," + groups.Append(""); // " notes STRING," + groups.Append(groupInfo.wellPosX); // " pos_on_plate_x UINTEGER," + groups.Append(groupInfo.wellPosY); // " pos_on_plate_y UINTEGER," + groups.EndRow(); + addedGroups.emplace(groupInfo.groupId); + } + } + + // Image + { + images.BeginRow(); + images.Append(imageId); // " image_id UBIGINT," + images.Append(imagePath.filename().string()); // " file_name TEXT," + images.Append(imagePath.string()); // " original_file_path TEXT + images.Append(ome.getNrOfChannels()); // " nr_of_c_stacks UINTEGER + images.Append(ome.getNrOfZStack()); // " nr_of_z_stacks UINTEGER + images.Append(ome.getNrOfTStack()); // " nr_of_t_stacks UINTEGER + images.Append(std::get<0>(ome.getSize())); // " width UINTEGER," + images.Append(std::get<1>(ome.getSize())); // " height UINTEGER," + images.Append(0); // " validity UBIGINT," + images.Append(false); // " processed BOOL," + images.EndRow(); + } + + // Image Group + { + images_groups.BeginRow(); + images_groups.Append(plateId); // " plate_id USMALLINT," + images_groups.Append(groupInfo.groupId); // " group_id USMALLINT," + images_groups.Append(imageId); // " image_id UBIGINT," + images_groups.Append(groupInfo.imageIdx); // " image_group_idx UINTEGER, " + images_groups.EndRow(); + } + + // Image channel + { + for(const auto &[channelId, channel] : ome.getChannelInfos()) { + images_channels.BeginRow(); + images_channels.Append(imageId); // " image_id UBIGINT," + images_channels.Append(channelId); // " stack_c UINTEGER, " + images_channels.Append(channel.channelId); // " channel_id TEXT," + images_channels.Append(channel.name); // " name TEXT," + images_channels.EndRow(); + } + } } - } - - // Image - { - images.BeginRow(); - images.Append(imageId); // " image_id UBIGINT," - images.Append(imagePath.filename().string()); // " file_name TEXT," - images.Append(imagePath.string()); // " original_file_path TEXT - images.Append(ome.getNrOfChannels()); // " nr_of_c_stacks UINTEGER - images.Append(ome.getNrOfZStack()); // " nr_of_z_stacks UINTEGER - images.Append(ome.getNrOfTStack()); // " nr_of_t_stacks UINTEGER - images.Append(std::get<0>(ome.getSize())); // " width UINTEGER," - images.Append(std::get<1>(ome.getSize())); // " height UINTEGER," - images.Append(0); // " validity UBIGINT," - images.Append(false); // " processed BOOL," - images.EndRow(); - } - - // Image Group - { - images_groups.BeginRow(); - images_groups.Append(plateId); // " plate_id USMALLINT," - images_groups.Append(groupInfo.groupId); // " group_id USMALLINT," - images_groups.Append(imageId); // " image_id UBIGINT," - images_groups.Append(groupInfo.imageIdx); // " image_group_idx UINTEGER, " - images_groups.EndRow(); - } + }; - // Image channel - { - for(const auto &[channelId, channel] : ome.getChannelInfos()) { - images_channels.BeginRow(); - images_channels.Append(imageId); // " image_id UBIGINT," - images_channels.Append(channelId); // " stack_c UINTEGER, " - images_channels.Append(channel.channelId); // " channel_id TEXT," - images_channels.Append(channel.name); // " name TEXT," - images_channels.EndRow(); - } - } + prepareFuture.push_back(globalThreadPool.submit_task(prepareImage)); } + prepareFuture.wait(); + groups.Close(); images.Close(); images_groups.Close(); @@ -665,7 +685,11 @@ bool Database::insertExperiment(const joda::settings::ExperimentSettings &exp) auto Database::selectExperiment() -> AnalyzeMeta { joda::settings::ExperimentSettings exp; - std::chrono::system_clock::time_point timestamp; + std::chrono::system_clock::time_point timestampStart; + std::chrono::system_clock::time_point timestampFinish; + std::string settingsString; + std::string jobName; + { std::unique_ptr result = select("SELECT experiment_id,name,notes FROM experiment"); if(result->HasError()) { @@ -676,24 +700,43 @@ auto Database::selectExperiment() -> AnalyzeMeta if(materializedResult->RowCount() > 0) { exp.experimentId = materializedResult->GetValue(0, 0).GetValue(); exp.experimentName = materializedResult->GetValue(1, 0).GetValue(); - exp.experimentName = materializedResult->GetValue(1, 0).GetValue(); + exp.notes = materializedResult->GetValue(2, 0).GetValue(); } } { - std::unique_ptr resultJobs = select("SELECT time_started FROM jobs ORDER BY time_started"); + std::unique_ptr resultJobs = select("SELECT time_started,time_finished,settings,job_name FROM jobs ORDER BY time_started"); if(resultJobs->HasError()) { throw std::invalid_argument(resultJobs->GetError()); } auto materializedResult = resultJobs->Cast().Materialize(); if(materializedResult->RowCount() > 0) { - auto timestampDb = materializedResult->GetValue(0, 0).GetValue(); - time_t epochTime = duckdb::Timestamp::GetEpochSeconds(timestampDb); - timestamp = std::chrono::system_clock::from_time_t(epochTime); + { + auto timestampDb = materializedResult->GetValue(0, 0).GetValue(); + time_t epochTime = duckdb::Timestamp::GetEpochSeconds(timestampDb); + timestampStart = std::chrono::system_clock::from_time_t(epochTime); + } + { + auto timestampDb = materializedResult->GetValue(1, 0).GetValue(); + time_t epochTime = duckdb::Timestamp::GetEpochSeconds(timestampDb); + timestampFinish = std::chrono::system_clock::from_time_t(epochTime); + } + + { + settingsString = helper::base64Decode(materializedResult->GetValue(2, 0).GetValue()); + } + + { + jobName = materializedResult->GetValue(3, 0).GetValue(); + } } } - return {.experiment = exp, .timestamp = timestamp}; + return {.experiment = exp, + .timestampStart = timestampStart, + .timestampFinish = timestampFinish, + .jobName = jobName, + .analyzeSettingsJsonString = settingsString}; } /// @@ -718,13 +761,13 @@ std::string Database::insertJobAndPlates(const joda::settings::AnalyzeSettings & duckdb::timestamp_t(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()); duckdb::timestamp_t nil = {}; auto prepare = connection->Prepare( - "INSERT INTO jobs (experiment_id, job_id,job_name, time_started, time_finished, settings) VALUES (?, ?, ?, ?, " + "INSERT INTO jobs (experiment_id, job_id, job_name,imagec_version, time_started, time_finished, settings) VALUES (?, ?, ?, ?, " "?, " "?)"); - nlohmann::json json = exp; - prepare->Execute(duckdb::Value::UUID(exp.projectSettings.experimentSettings.experimentId), jobId, jobName, - duckdb::Value::TIMESTAMP(timestampStart), duckdb::Value::TIMESTAMP(nil), static_cast(json.dump())); + prepare->Execute(duckdb::Value::UUID(exp.projectSettings.experimentSettings.experimentId), jobId, jobName, std::string(Version::getVersion()), + duckdb::Value::TIMESTAMP(timestampStart), duckdb::Value::TIMESTAMP(nil), + helper::base64Encode(settings::Settings::toString(exp))); } catch(const std::exception &ex) { connection->Rollback(); throw std::runtime_error(ex.what()); diff --git a/src/backend/helper/database/database.hpp b/src/backend/helper/database/database.hpp index a6b029ed..8c6674e1 100644 --- a/src/backend/helper/database/database.hpp +++ b/src/backend/helper/database/database.hpp @@ -21,6 +21,7 @@ #include "backend/enums/types.hpp" #include "backend/helper/file_grouper/file_grouper_types.hpp" #include "backend/helper/ome_parser/ome_info.hpp" +#include "backend/helper/threadpool/thread_pool.hpp" #include "backend/processor/context/image_context.hpp" #include "backend/settings/analze_settings.hpp" #include "backend/settings/project_settings/experiment_settings.hpp" @@ -37,7 +38,10 @@ namespace joda::db { struct AnalyzeMeta { joda::settings::ExperimentSettings experiment; - std::chrono::system_clock::time_point timestamp; + std::chrono::system_clock::time_point timestampStart; + std::chrono::system_clock::time_point timestampFinish; + std::string jobName; + std::string analyzeSettingsJsonString; }; struct ImageInfo @@ -60,8 +64,8 @@ class Database std::string startJob(const joda::settings::AnalyzeSettings &, const std::string &jobName); void finishJob(const std::string &jobId); - auto prepareImages(uint8_t plateId, enums::GroupBy groupBy, const std::string &filenameRegex, const std::vector &imagePaths) - -> std::vector>; + auto prepareImages(uint8_t plateId, enums::GroupBy groupBy, const std::string &filenameRegex, const std::vector &imagePaths, + BS::thread_pool &globalThreadPool) -> std::vector>; void setImageProcessed(uint64_t); void insertGroup(uint16_t plateId, const joda::grp::GroupInformation &groupInfo); diff --git a/src/backend/helper/database/exporter/exporter.cpp b/src/backend/helper/database/exporter/exporter.cpp index dc07c24f..ef529ea7 100644 --- a/src/backend/helper/database/exporter/exporter.cpp +++ b/src/backend/helper/database/exporter/exporter.cpp @@ -6,10 +6,12 @@ #include "backend/enums/enum_measurements.hpp" #include "backend/enums/enums_classes.hpp" #include "backend/enums/enums_clusters.hpp" +#include "backend/enums/enums_grouping.hpp" #include "backend/helper/database/plugins/helper.hpp" #include "backend/helper/database/plugins/stats_for_image.hpp" #include "backend/helper/database/plugins/stats_for_plate.hpp" #include "backend/helper/database/plugins/stats_for_well.hpp" +#include "backend/settings/analze_settings.hpp" namespace joda::db { @@ -20,10 +22,13 @@ namespace joda::db { /// \param[out] /// \return /// -void BatchExporter::startExport(const Settings &settings, const std::string &outputFileName) +void BatchExporter::startExport(const Settings &settings, const settings::AnalyzeSettings &analyzeSettings, const std::string &jobName, + std::chrono::system_clock::time_point timeStarted, std::chrono::system_clock::time_point timeFinished, + const std::string &outputFileName) { setlocale(LC_NUMERIC, "C"); // Needed for correct comma in libxlsx auto workbookSettings = createWorkBook(outputFileName); + createAnalyzeSettings(workbookSettings, analyzeSettings, jobName, timeStarted, timeFinished); switch(settings.exportType) { case Settings::ExportType::HEATMAP: createHeatmapSummary(workbookSettings, settings); @@ -61,8 +66,7 @@ void BatchExporter::createHeatmapSummary(WorkBook &workbookSettings, const Setti worksheetName.resize(leftChars); } - actWorkSheet = workbook_add_worksheet(workbookSettings.workbook, - static_cast(worksheetName + worksheetNameSuffix).data()); + actWorkSheet = workbook_add_worksheet(workbookSettings.workbook, static_cast(worksheetName + worksheetNameSuffix).data()); offsets.col = 0; offsets.row = 0; }; @@ -75,11 +79,10 @@ void BatchExporter::createHeatmapSummary(WorkBook &workbookSettings, const Setti for(const auto &[measureChannelId, statsIn] : imageChannel.measureChannels) { for(const auto stats : statsIn) { - auto generate = [&, clusterId = clusterAndClassId.clusterId, classId = clusterAndClassId.classId, - className = imageChannel.className, measureChannelId = measureChannelId]( - uint32_t cStack, const std::string &crossChannelChannelCName, - std::pair crossChannelCluster, - std::pair crossChannelClass) { + auto generate = [&, clusterId = clusterAndClassId.clusterId, classId = clusterAndClassId.classId, className = imageChannel.className, + measureChannelId = measureChannelId](uint32_t cStack, const std::string &crossChannelChannelCName, + std::pair crossChannelCluster, + std::pair crossChannelClass) { table::Table table; auto filter = joda::db::QueryFilter{.analyzer = &settings.analyzer, .plateRows = settings.plateRows, @@ -112,8 +115,8 @@ void BatchExporter::createHeatmapSummary(WorkBook &workbookSettings, const Setti table = joda::db::StatsPerImage::toHeatmap(filter); break; } - paintPlateBorder(actWorkSheet, table.getRows(), table.getCols(), offsets.row, workbookSettings.header, - workbookSettings.merge_format, workbookSettings.numberFormat, createHeader(filter)); + paintPlateBorder(actWorkSheet, table.getRows(), table.getCols(), offsets.row, workbookSettings.header, workbookSettings.merge_format, + workbookSettings.numberFormat, createHeader(filter)); offsets = paintHeatmap(workbookSettings, actWorkSheet, table, offsets.row); offsets.row += 4; }; @@ -124,8 +127,7 @@ void BatchExporter::createHeatmapSummary(WorkBook &workbookSettings, const Setti } } else if(getType(measureChannelId) == joda::db::MeasureType::COUNT) { for(const auto &[clusterAndClassCrossChannel, name] : imageChannel.crossChannelCount) { - generate(0, "", {clusterAndClassCrossChannel.clusterId, std::get<0>(name)}, - {clusterAndClassCrossChannel.classId, std::get<1>(name)}); + generate(0, "", {clusterAndClassCrossChannel.clusterId, std::get<0>(name)}, {clusterAndClassCrossChannel.classId, std::get<1>(name)}); } } else { @@ -162,10 +164,8 @@ void BatchExporter::createListSummary(WorkBook &workbookSettings, const Settings loopCount++; if(actClusterId != clusterAndClassId.clusterId) { actClusterId = clusterAndClassId.clusterId; - worksheet_merge_range(worksheet, 0, colOffset + COL_OFFSET - tmpCols, 0, colOffset + COL_OFFSET - 1, "-", - workbookSettings.merge_format); - worksheet_write_string(worksheet, 0, colOffset + COL_OFFSET - tmpCols, actImageClusterName.data(), - workbookSettings.header); + worksheet_merge_range(worksheet, 0, colOffset + COL_OFFSET - tmpCols, 0, colOffset + COL_OFFSET - 1, "-", workbookSettings.merge_format); + worksheet_write_string(worksheet, 0, colOffset + COL_OFFSET - tmpCols, actImageClusterName.data(), workbookSettings.header); colOffset++; tmpCols = 0; actImageClusterName = imageChannel.clusterName; @@ -173,11 +173,10 @@ void BatchExporter::createListSummary(WorkBook &workbookSettings, const Settings for(const auto &[measureChannelId, statsIn] : imageChannel.measureChannels) { for(const auto stats : statsIn) { - auto generate = [&, clusterId = clusterAndClassId.clusterId, classId = clusterAndClassId.classId, - className = imageChannel.className, measureChannelId = measureChannelId]( - uint32_t cStack, const std::string &crossChannelChannelCName, - std::pair crossChannelCluster, - std::pair crossChannelClass) { + auto generate = [&, clusterId = clusterAndClassId.clusterId, classId = clusterAndClassId.classId, className = imageChannel.className, + measureChannelId = measureChannelId](uint32_t cStack, const std::string &crossChannelChannelCName, + std::pair crossChannelCluster, + std::pair crossChannelClass) { auto filter = joda::db::QueryFilter{.analyzer = &settings.analyzer, .plateRows = settings.plateRows, .plateCols = settings.plateCols, @@ -216,13 +215,11 @@ void BatchExporter::createListSummary(WorkBook &workbookSettings, const Settings } for(int col = 0; col < table.getCols(); col++) { - worksheet_write_string(worksheet, 1, colOffset + COL_OFFSET, table.getMutableColHeader()[col].data(), - workbookSettings.header); + worksheet_write_string(worksheet, 1, colOffset + COL_OFFSET, table.getMutableColHeader()[col].data(), workbookSettings.header); for(int row = 0; row < table.getRows(); row++) { // Row header - worksheet_write_string(worksheet, 1 + ROW_OFFSET + row, 0, table.getRowHeader(row).data(), - workbookSettings.header); + worksheet_write_string(worksheet, 1 + ROW_OFFSET + row, 0, table.getRowHeader(row).data(), workbookSettings.header); auto *format = workbookSettings.numberFormat; if(!table.data(row, col).isValid()) { @@ -243,8 +240,7 @@ void BatchExporter::createListSummary(WorkBook &workbookSettings, const Settings } } else if(getType(measureChannelId) == joda::db::MeasureType::COUNT) { for(const auto &[clusterAndClassCrossChannel, name] : imageChannel.crossChannelCount) { - generate(0, "", {clusterAndClassCrossChannel.clusterId, std::get<0>(name)}, - {clusterAndClassCrossChannel.classId, std::get<1>(name)}); + generate(0, "", {clusterAndClassCrossChannel.clusterId, std::get<0>(name)}, {clusterAndClassCrossChannel.classId, std::get<1>(name)}); } } else { generate(0, "", {}, {}); @@ -253,10 +249,8 @@ void BatchExporter::createListSummary(WorkBook &workbookSettings, const Settings } if(settings.clustersToExport.size() == loopCount) { - worksheet_merge_range(worksheet, 0, colOffset + COL_OFFSET - tmpCols, 0, colOffset + COL_OFFSET - 1, "-", - workbookSettings.merge_format); - worksheet_write_string(worksheet, 0, colOffset + COL_OFFSET - tmpCols, imageChannel.clusterName.data(), - workbookSettings.header); + worksheet_merge_range(worksheet, 0, colOffset + COL_OFFSET - tmpCols, 0, colOffset + COL_OFFSET - 1, "-", workbookSettings.merge_format); + worksheet_write_string(worksheet, 0, colOffset + COL_OFFSET - tmpCols, imageChannel.clusterName.data(), workbookSettings.header); } } } @@ -368,18 +362,9 @@ BatchExporter::WorkBook BatchExporter::createWorkBook(std::string outputFileName // format_set_border(numberFormatInvalid, LXW_BORDER_THIN); // Set border style to thin format_set_diag_type(numberFormatInvalidScientific, LXW_DIAGONAL_BORDER_UP_DOWN); - return WorkBook{workbook, - header, - headerInvalid, - imageHeaderHyperlinkFormat, - imageHeaderHyperlinkFormatInvalid, - merge_format, - headerBold, - fontNormal, - numberFormat, - numberFormatInvalid, - numberFormatScientific, - numberFormatInvalidScientific}; + return WorkBook{ + workbook, header, headerInvalid, imageHeaderHyperlinkFormat, imageHeaderHyperlinkFormatInvalid, merge_format, headerBold, + fontNormal, numberFormat, numberFormatInvalid, numberFormatScientific, numberFormatInvalidScientific}; } /// @@ -389,9 +374,8 @@ BatchExporter::WorkBook BatchExporter::createWorkBook(std::string outputFileName /// \param[out] /// \return /// -void BatchExporter::paintPlateBorder(lxw_worksheet *sheet, int64_t rows, int64_t cols, int32_t rowOffset, - lxw_format *header, lxw_format *numberFormat, lxw_format *mergeFormat, - const std::string &title) +void BatchExporter::paintPlateBorder(lxw_worksheet *sheet, int64_t rows, int64_t cols, int32_t rowOffset, lxw_format *header, + lxw_format *numberFormat, lxw_format *mergeFormat, const std::string &title) { const int32_t ROW_OFFSET = 2; const int32_t HEADER_CELL_SIZE = 15; @@ -436,12 +420,11 @@ void BatchExporter::paintPlateBorder(lxw_worksheet *sheet, int64_t rows, int64_t worksheet_write_string(sheet, row + rowOffset + ROW_OFFSET, cols + 1, toWrt, header); } - worksheet_conditional_format_range(sheet, rowOffset + ROW_OFFSET, 1, rowOffset + rows + ROW_OFFSET, 1 + cols, - condFormat); + worksheet_conditional_format_range(sheet, rowOffset + ROW_OFFSET, 1, rowOffset + rows + ROW_OFFSET, 1 + cols, condFormat); } -BatchExporter::Pos BatchExporter::paintHeatmap(const WorkBook &workbookSettings, lxw_worksheet *worksheet, - const joda::table::Table &table, uint32_t rowOffset) +BatchExporter::Pos BatchExporter::paintHeatmap(const WorkBook &workbookSettings, lxw_worksheet *worksheet, const joda::table::Table &table, + uint32_t rowOffset) { const int32_t ROW_OFFSET = 2; @@ -459,4 +442,84 @@ BatchExporter::Pos BatchExporter::paintHeatmap(const WorkBook &workbookSettings, return BatchExporter::Pos{.row = table.getRows() + rowOffset + ROW_OFFSET, .col = table.getCols()}; } +/// +/// \brief Add analyze settings to excel +/// \author +/// \param[in] +/// \param[out] +/// \return +/// +void BatchExporter::createAnalyzeSettings(WorkBook &workbookSettings, const settings::AnalyzeSettings &settings, const std::string &jobName, + std::chrono::system_clock::time_point timeStarted, std::chrono::system_clock::time_point timeFinished) +{ + auto *sheet = workbook_add_worksheet(workbookSettings.workbook, "Analyze"); + + worksheet_set_column_pixels(sheet, 0, 0, 200, NULL); + worksheet_set_column_pixels(sheet, 1, 1, 200, NULL); + + int32_t rowOffset = 0; + auto addTitle = [&sheet, &rowOffset, &workbookSettings](const std::string &title) { + rowOffset++; + worksheet_merge_range(sheet, rowOffset, 0, rowOffset, 1, "-", workbookSettings.headerBold); + worksheet_write_string(sheet, rowOffset, 0, title.data(), workbookSettings.headerBold); + rowOffset++; + }; + + auto addElement = [&sheet, &rowOffset, &workbookSettings](const std::string &title, const std::string &value) { + worksheet_write_string(sheet, rowOffset, 0, title.data(), workbookSettings.headerBold); + worksheet_write_string(sheet, rowOffset, 1, value.data(), workbookSettings.fontNormal); + rowOffset++; + }; + + addTitle("Date"); + addElement("Started at", joda::helper::timepointToIsoString(timeStarted)); + addElement("Finished at", joda::helper::timepointToIsoString(timeFinished)); + addElement("Duration at", joda::helper::getDurationAsString(timeStarted, timeFinished)); + + addTitle("ImageC"); + addElement("Version", settings.meta.imagecVersion); + addElement("Build", settings.meta.buildTime); + + addTitle("Clusters"); + for(const auto &cluster : settings.projectSettings.clusters) { + nlohmann::json classIdStr = static_cast(cluster.clusterId); + addElement(std::string(classIdStr), cluster.name); + } + + addTitle("Classes"); + for(const auto &classs : settings.projectSettings.classes) { + nlohmann::json classIdStr = static_cast(classs.classId); + addElement(std::string(classIdStr), classs.name); + } + + addTitle("Pipelines"); + for(const auto &pipeline : settings.pipelines) { + addElement("Name", pipeline.meta.name); + + nlohmann::json clusterId = static_cast(pipeline.pipelineSetup.defaultClusterId); + addElement("Default cluster", std::string(clusterId)); + + nlohmann::json classId = static_cast(pipeline.pipelineSetup.defaultClassId); + addElement("Default class", std::string(classId)); + } + + addTitle("Project settings"); + addElement("Scientist", settings.projectSettings.address.firstName + " " + settings.projectSettings.address.lastName); + addElement("Organisation", settings.projectSettings.address.organization); + addElement("Experiment ID", settings.projectSettings.experimentSettings.experimentId); + addElement("Experiment name", settings.projectSettings.experimentSettings.experimentName); + addElement("Job name", jobName); + addElement("Notes", settings.projectSettings.experimentSettings.notes); + addElement("Working directory", settings.projectSettings.workingDirectory); + + for(const auto &plate : settings.projectSettings.plates) { + addTitle("Plate " + std::to_string(plate.plateId)); + addElement("Filename regex", plate.filenameRegex); + addElement("Image folder", plate.imageFolder); + addElement("Well order", joda::settings::vectorToString(plate.wellImageOrder)); + nlohmann::json groupBy = static_cast(plate.groupBy); + addElement("Group by", std::string(groupBy)); + } +} + } // namespace joda::db diff --git a/src/backend/helper/database/exporter/exporter.hpp b/src/backend/helper/database/exporter/exporter.hpp index d1a4beb1..67e6a6fd 100644 --- a/src/backend/helper/database/exporter/exporter.hpp +++ b/src/backend/helper/database/exporter/exporter.hpp @@ -12,6 +12,7 @@ #include "backend/enums/enums_clusters.hpp" #include "backend/helper/database/database.hpp" #include "backend/helper/table/table.hpp" +#include "backend/settings/analze_settings.hpp" namespace joda::db { @@ -55,7 +56,9 @@ class BatchExporter ExportDetail exportDetail; }; - static void startExport(const Settings &settings, const std::string &outputFileName); + static void startExport(const Settings &settings, const settings::AnalyzeSettings &analyzeSettings, const std::string &jobName, + std::chrono::system_clock::time_point timeStarted, std::chrono::system_clock::time_point timeFinished, + const std::string &outputFileName); private: ///////////////////////////////////////////////////// @@ -82,14 +85,13 @@ class BatchExporter uint32_t col = 0; }; static WorkBook createWorkBook(std::string outputFileName); + static void createAnalyzeSettings(WorkBook &, const settings::AnalyzeSettings &settings, const std::string &jobName, + std::chrono::system_clock::time_point timeStarted, std::chrono::system_clock::time_point timeFinished); static void createHeatmapSummary(WorkBook &, const Settings &settings); static void createListSummary(WorkBook &workbookSettings, const Settings &settings); - - static void paintPlateBorder(lxw_worksheet *sheet, int64_t rows, int64_t cols, int32_t rowOffset, lxw_format *header, - lxw_format *numberFormat, lxw_format *mergeFormat, const std::string &title); - - static Pos paintHeatmap(const WorkBook &workbookSettings, lxw_worksheet *worksheet, const joda::table::Table &table, - uint32_t rowOffset); + static void paintPlateBorder(lxw_worksheet *sheet, int64_t rows, int64_t cols, int32_t rowOffset, lxw_format *header, lxw_format *numberFormat, + lxw_format *mergeFormat, const std::string &title); + static Pos paintHeatmap(const WorkBook &workbookSettings, lxw_worksheet *worksheet, const joda::table::Table &table, uint32_t rowOffset); }; } // namespace joda::db diff --git a/src/backend/helper/database/plugins/stats_for_image.cpp b/src/backend/helper/database/plugins/stats_for_image.cpp index 1bb5a710..b131d0f5 100644 --- a/src/backend/helper/database/plugins/stats_for_image.cpp +++ b/src/backend/helper/database/plugins/stats_for_image.cpp @@ -32,12 +32,12 @@ auto StatsPerImage::toTable(const QueryFilter &filter) -> joda::table::Table auto buildStats = [&]() { return getMeasurement(filter.measurementChannel) + " as val"; }; auto queryMeasure = [&]() { - std::unique_ptr stats = filter.analyzer->select( - "SELECT " + buildStats() + - " FROM objects " - "WHERE" - " objects.image_id=$1 AND objects.cluster_id=$2 AND objects.class_id=$3 ", - filter.actImageId, static_cast(filter.clusterId), static_cast(filter.classId)); + std::unique_ptr stats = + filter.analyzer->select("SELECT " + buildStats() + + " FROM objects " + "WHERE" + " objects.image_id=$1 AND objects.cluster_id=$2 AND objects.class_id=$3 ", + filter.actImageId, static_cast(filter.clusterId), static_cast(filter.classId)); return stats; }; @@ -51,15 +51,11 @@ auto StatsPerImage::toTable(const QueryFilter &filter) -> joda::table::Table " WHERE" " objects.image_id=$1 AND objects.cluster_id=$2 AND objects.class_id=$3 AND " "object_measurements.meas_stack_c = $4", - filter.actImageId, static_cast(filter.clusterId), static_cast(filter.classId), - filter.crossChanelStack_c); + filter.actImageId, static_cast(filter.clusterId), static_cast(filter.classId), filter.crossChanelStack_c); return stats; }; auto queryIntersectingMeasure = [&]() { - std::cout << "Cross channel " << std::to_string((uint16_t) filter.clusterId) << " | " - << std::to_string((uint16_t) filter.crossChannelClusterId) << std::endl; - std::unique_ptr stats = filter.analyzer->select( "SELECT " "COUNT(inners.meas_object_id) " @@ -127,8 +123,7 @@ auto StatsPerImage::toHeatmap(const QueryFilter &filter) -> joda::table::Table results.getMutableRowHeader()[row] = std::to_string(row + 1); for(uint64_t col = 0; col < imageInfo.width; col++) { results.getMutableColHeader()[col] = std::to_string(col + 1); - results.setData(row, col, - table::TableCell{std::numeric_limits::quiet_NaN(), 0, false, imageInfo.controlImgPath}); + results.setData(row, col, table::TableCell{std::numeric_limits::quiet_NaN(), 0, false, imageInfo.controlImgPath}); } } @@ -173,8 +168,7 @@ auto StatsPerImage::toHeatmapList(const QueryFilter &filter) -> joda::table::Tab uint32_t key = ((row << 16) & 0xFFFF0000) | (col & 0xFFFF); char letter = 'A' + row; results.getMutableRowHeader()[tableRowCount] = std::string(1, letter) + "" + std::to_string(col + 1); - results.setData(tableRowCount, 0, - table::TableCell{std::numeric_limits::quiet_NaN(), 0, false, imageInfo.controlImgPath}); + results.setData(tableRowCount, 0, table::TableCell{std::numeric_limits::quiet_NaN(), 0, false, imageInfo.controlImgPath}); sortedData.emplace(key, std::pair{std::numeric_limits::quiet_NaN(), ""}); tableRowCount++; } @@ -252,9 +246,7 @@ auto StatsPerImage::densityMap(const QueryFilter &filter) -> std::tuple result = filter.analyzer->select( diff --git a/src/backend/helper/duration_count/duration_count.cpp b/src/backend/helper/duration_count/duration_count.cpp index cf4f36d1..213b83ab 100644 --- a/src/backend/helper/duration_count/duration_count.cpp +++ b/src/backend/helper/duration_count/duration_count.cpp @@ -84,8 +84,6 @@ void DurationCount::printStats(double nrOfImages, const std::filesystem::path &o // Close the file outputFile.close(); - - std::cout << "String successfully written to file '" << filename << "'." << std::endl; } else { std::cerr << "Error: Unable to open file '" << filename << "' for writing." << std::endl; } diff --git a/src/backend/helper/helper.hpp b/src/backend/helper/helper.hpp index 79def5ca..79f57ab8 100644 --- a/src/backend/helper/helper.hpp +++ b/src/backend/helper/helper.hpp @@ -109,6 +109,22 @@ inline std::string timepointToIsoString(const std::chrono::system_clock::time_po return std::string(buffer); } +inline std::string getDurationAsString(const std::chrono::system_clock::time_point &t1, const std::chrono::system_clock::time_point &t2) +{ + // Calculate the duration between t1 and t2 in seconds + auto duration = std::chrono::duration_cast(t2 - t1); + + // Extract minutes and seconds + auto minutes = std::chrono::duration_cast(duration).count(); + auto seconds = duration.count() % 60; + + // Create a formatted string + std::ostringstream oss; + oss << minutes << " min. " << seconds << " sec."; + + return oss.str(); +} + } // namespace joda::helper namespace joda::types { diff --git a/src/backend/helper/reader/image_reader.cpp b/src/backend/helper/reader/image_reader.cpp index c3c9f292..474b62c2 100644 --- a/src/backend/helper/reader/image_reader.cpp +++ b/src/backend/helper/reader/image_reader.cpp @@ -83,7 +83,7 @@ void ImageReader::setPath() path = javaBin + ":" + path; setenv("PATH", path.c_str(), 1); #else - std::string javaHome = "java/jre_win"; + std::string javaHome = "java/jre_linux"; std::string javaBin = javaHome + "/bin"; setenv("JAVA_HOME", javaHome.c_str(), 1); std::string path = std::getenv("PATH"); @@ -116,6 +116,7 @@ void ImageReader::init() if(jvmDll == NULL) { std::cerr << "Failed to load jvm lib" << std::endl; + return; } #ifdef _WIN32 @@ -124,6 +125,11 @@ void ImageReader::init() JNI_CreateJavaVM = reinterpret_cast(dlsym(jvmDll, JNI_CREATEVM)); #endif + if(JNI_CreateJavaVM == nullptr) { + std::cout << "Could not JNI_CreateJavaV" << std::endl; + return; + } + try { /* Set the version field of the initialization arguments for JNI v1.4. */ initArgs.version = JNI_VERSION_1_8; @@ -221,7 +227,7 @@ std::string ImageReader::getJavaVersion() cv::Mat ImageReader::loadEntireImage(const std::string &filename, const Plane &imagePlane, uint16_t series, uint16_t resolutionIdx) { // Takes 150 ms - if(mJVMInitialised && imagePlane.c >= 0 && imagePlane.z >= 0 && imagePlane.t >= 0) { + if(myJVM != nullptr && mJVMInitialised && imagePlane.c >= 0 && imagePlane.z >= 0 && imagePlane.t >= 0) { // std::lock_guard lock(mReadMutex); JNIEnv *myEnv; @@ -270,7 +276,7 @@ cv::Mat ImageReader::loadEntireImage(const std::string &filename, const Plane &i cv::Mat ImageReader::loadThumbnail(const std::string &filename, const Plane &imagePlane, uint16_t series) { // Takes 150 ms - if(mJVMInitialised && imagePlane.c >= 0 && imagePlane.z >= 0 && imagePlane.t >= 0) { + if(nullptr != myJVM && mJVMInitialised && imagePlane.c >= 0 && imagePlane.z >= 0 && imagePlane.t >= 0) { // std::lock_guard lock(mReadMutex); auto ome = getOmeInformation(filename); @@ -367,7 +373,7 @@ cv::Mat ImageReader::loadThumbnail(const std::string &filename, const Plane &ima cv::Mat ImageReader::loadImageTile(const std::string &filename, const Plane &imagePlane, uint16_t series, uint16_t resolutionIdx, const joda::ome::TileToLoad &tile) { - if(mJVMInitialised && imagePlane.c >= 0 && imagePlane.z >= 0 && imagePlane.t >= 0) { + if(nullptr != myJVM && mJVMInitialised && imagePlane.c >= 0 && imagePlane.z >= 0 && imagePlane.t >= 0) { JNIEnv *myEnv = nullptr; myJVM->AttachCurrentThread(reinterpret_cast(&myEnv), nullptr); jstring filePath = myEnv->NewStringUTF(filename.c_str()); @@ -434,7 +440,7 @@ cv::Mat ImageReader::loadImageTile(const std::string &filename, const Plane &ima auto ImageReader::getOmeInformation(const std::filesystem::path &filename) -> joda::ome::OmeInfo { const int32_t series = 0; - if(mJVMInitialised) { + if(nullptr != myJVM && mJVMInitialised) { auto id = DurationCount::start("Get OEM"); JNIEnv *myEnv; myJVM->AttachCurrentThread((void **) &myEnv, NULL); diff --git a/src/backend/processor/processor.cpp b/src/backend/processor/processor.cpp index 5251491d..7b64b89a 100644 --- a/src/backend/processor/processor.cpp +++ b/src/backend/processor/processor.cpp @@ -94,7 +94,7 @@ void Processor::execute(const joda::settings::AnalyzeSettings &program, const st const auto &images = allImages.getFilesListAt(plate.plateId); mProgress.setRunningPreparingPipeline(); - auto imagesToProcess = db.prepareImages(plate.plateId, plate.groupBy, plate.filenameRegex, images); + auto imagesToProcess = db.prepareImages(plate.plateId, plate.groupBy, plate.filenameRegex, images, mGlobThreadPool); mProgress.setStateRunning(); // diff --git a/src/backend/settings/analze_settings.hpp b/src/backend/settings/analze_settings.hpp index 0137e317..154140ec 100644 --- a/src/backend/settings/analze_settings.hpp +++ b/src/backend/settings/analze_settings.hpp @@ -23,6 +23,7 @@ #include #include #include "backend/enums/enums_file_endians.hpp" +#include "backend/settings/program/program_meta.hpp" #include "pipeline/pipeline.hpp" #include "project_settings/project_settings.hpp" #include @@ -36,6 +37,7 @@ class AnalyzeSettings final ProjectSettings projectSettings; ProjectImageSetup imageSetup; std::list pipelines; + ProgramMeta meta; [[nodiscard]] const std::string &schema() const { @@ -48,8 +50,8 @@ class AnalyzeSettings final auto checkForErrors() const -> std::vector>; private: - std::string configSchema = "https://imagec.org/schemas/v1/analyze-settings" + joda::fs::EXT_PROJECT; + std::string configSchema = "https://imagec.org/schemas/v1/analyze-settings.json"; void check() const; - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT_EXTENDED(AnalyzeSettings, projectSettings, imageSetup, pipelines); + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT_EXTENDED(AnalyzeSettings, projectSettings, imageSetup, pipelines, meta); }; } // namespace joda::settings diff --git a/src/backend/settings/program/program_meta.hpp b/src/backend/settings/program/program_meta.hpp new file mode 100644 index 00000000..bf169b94 --- /dev/null +++ b/src/backend/settings/program/program_meta.hpp @@ -0,0 +1,34 @@ +/// +/// \file program_meta.hpp +/// \author Joachim Danmayr +/// \date 2024-10-10 +/// +/// \copyright Copyright 2019 Joachim Danmayr +/// All rights reserved! This file is subject +/// to the terms and conditions defined in file +/// LICENSE.txt, which is part of this package. +/// +/// + +#pragma once + +#include +#include "backend/settings/setting.hpp" +#include +#include "version.h" + +namespace joda::settings { + +struct ProgramMeta final +{ + std::string imagecVersion = Version::getVersion(); + std::string buildTime = Version::getBuildTime(); + + void check() const + { + } + + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT_EXTENDED(ProgramMeta, imagecVersion); +}; + +} // namespace joda::settings diff --git a/src/backend/settings/project_settings/experiment_settings.hpp b/src/backend/settings/project_settings/experiment_settings.hpp index 2d91720d..786df9d7 100644 --- a/src/backend/settings/project_settings/experiment_settings.hpp +++ b/src/backend/settings/project_settings/experiment_settings.hpp @@ -27,7 +27,7 @@ struct ExperimentSettings std::string experimentId; // - // Unique name of the job + // Unique name of the experiment // std::string experimentName; diff --git a/src/backend/settings/settings.cpp b/src/backend/settings/settings.cpp index 52675805..897e32a7 100644 --- a/src/backend/settings/settings.cpp +++ b/src/backend/settings/settings.cpp @@ -44,9 +44,15 @@ void Settings::storeSettings(const std::filesystem::path &pathIn, const joda::se } } +std::string Settings::toString(const joda::settings::AnalyzeSettings &settings) +{ + nlohmann::json json = settings; + removeNullValues(json); + return json.dump(2); +} + /// \todo How to check incomplete settings -bool Settings::isEqual(const joda::settings::AnalyzeSettings &settingsOld, - const joda::settings::AnalyzeSettings &settingsNew) +bool Settings::isEqual(const joda::settings::AnalyzeSettings &settingsOld, const joda::settings::AnalyzeSettings &settingsNew) { try { nlohmann::json jsonOld = settingsOld; diff --git a/src/backend/settings/settings.hpp b/src/backend/settings/settings.hpp index 61db805b..f33078f6 100644 --- a/src/backend/settings/settings.hpp +++ b/src/backend/settings/settings.hpp @@ -10,9 +10,9 @@ namespace joda::settings { class Settings { public: + static std::string toString(const joda::settings::AnalyzeSettings &settings); static void storeSettings(const std::filesystem::path &pathIn, const joda::settings::AnalyzeSettings &settings); - static bool isEqual(const joda::settings::AnalyzeSettings &settingsOld, - const joda::settings::AnalyzeSettings &settingsNew); + static bool isEqual(const joda::settings::AnalyzeSettings &settingsOld, const joda::settings::AnalyzeSettings &settingsNew); }; } // namespace joda::settings diff --git a/src/ui/container/pipeline/add_command_button.cpp b/src/ui/container/pipeline/add_command_button.cpp index 19bd3867..f3de564f 100644 --- a/src/ui/container/pipeline/add_command_button.cpp +++ b/src/ui/container/pipeline/add_command_button.cpp @@ -26,7 +26,6 @@ AddCommandButtonBase::AddCommandButtonBase(joda::settings::Pipeline &settings, P setObjectName("addCommandButton"); setContentsMargins(0, 0, 0, 0); setFixedHeight(10); - mSelectionDialog = new DialogCommandSelection(mSettings, pipelineStepSettingsUi, mPipelineStepBefore, mOutOfStepBefore, mParent); } void AddCommandButtonBase::paintEvent(QPaintEvent *event) @@ -51,7 +50,13 @@ void AddCommandButtonBase::paintEvent(QPaintEvent *event) void AddCommandButtonBase::mousePressEvent(QMouseEvent *event) { + if(mSelectionDialog == nullptr) { + mSelectionDialog = new DialogCommandSelection(mSettings, pipelineStepSettingsUi, mPipelineStepBefore, mOutOfStepBefore, mParent); + } + mSelectionDialog->setInOutBefore(mOutOfStepBefore); mSelectionDialog->exec(); + delete mSelectionDialog; + mSelectionDialog = nullptr; } void AddCommandButtonBase::enterEvent(QEnterEvent *event) @@ -77,7 +82,6 @@ void AddCommandButtonBase::onAddCommandClicked() void AddCommandButtonBase::setInOutBefore(InOuts inout) { mOutOfStepBefore = inout; - mSelectionDialog->setInOutBefore(inout); } } // namespace joda::ui diff --git a/src/ui/container/pipeline/add_command_button.hpp b/src/ui/container/pipeline/add_command_button.hpp index 1001ec1d..5089aba4 100644 --- a/src/ui/container/pipeline/add_command_button.hpp +++ b/src/ui/container/pipeline/add_command_button.hpp @@ -52,7 +52,7 @@ public slots: joda::settings::Pipeline &mSettings; PanelPipelineSettings *pipelineStepSettingsUi; InOuts mOutOfStepBefore; - DialogCommandSelection *mSelectionDialog; + DialogCommandSelection *mSelectionDialog = nullptr; }; } // namespace joda::ui diff --git a/src/ui/container/pipeline/panel_pipeline_settings.cpp b/src/ui/container/pipeline/panel_pipeline_settings.cpp index 47e14d7b..da2ffa9e 100644 --- a/src/ui/container/pipeline/panel_pipeline_settings.cpp +++ b/src/ui/container/pipeline/panel_pipeline_settings.cpp @@ -114,7 +114,7 @@ PanelPipelineSettings::PanelPipelineSettings(WindowMain *wm, joda::settings::Pip // Tool button - auto *saveAsTemplateButton = mLayout.addActionButton("Save as template", generateIcon("mark-as-favorite")); + auto *saveAsTemplateButton = mLayout.addActionButton("Save as template", generateIcon("save")); connect(saveAsTemplateButton, &QAction::triggered, [this] { this->saveAsTemplate(); }); connect(this, &PanelPipelineSettings::updatePreviewStarted, this, &PanelPipelineSettings::onPreviewStarted); diff --git a/src/ui/results/dialog_export_data.cpp b/src/ui/results/dialog_export_data.cpp index 36e2af50..909b2ca8 100644 --- a/src/ui/results/dialog_export_data.cpp +++ b/src/ui/results/dialog_export_data.cpp @@ -47,6 +47,7 @@ #include "backend/helper/database/plugins/stats_for_image.hpp" #include "backend/helper/database/plugins/stats_for_plate.hpp" #include "backend/helper/database/plugins/stats_for_well.hpp" +#include "backend/settings/analze_settings.hpp" #include "ui/container/setting/setting_base.hpp" #include "ui/container/setting/setting_combobox_classification_unmanaged.hpp" #include "ui/container/setting/setting_combobox_multi_classification_unmanaged.hpp" @@ -263,9 +264,10 @@ std::map> ExportColumn::getMeasuremen /// \return /// DialogExportData::DialogExportData(std::unique_ptr &analyzer, const db::QueryFilter &filter, - const std::map &clustersAndClasses, QWidget *windowMain) : + const std::map &clustersAndClasses, db::AnalyzeMeta *analyzeMeta, + QWidget *windowMain) : QDialog(windowMain), - mWindowMain(windowMain), mAnalyzer(analyzer), mFilter(filter), mLayout(this, false, true, false, true) + mWindowMain(windowMain), mAnalyzer(analyzer), mAnalyzeMeta(analyzeMeta), mFilter(filter), mLayout(this, false, true, false, true) { setWindowTitle("Export data"); setMinimumHeight(700); @@ -276,12 +278,12 @@ DialogExportData::DialogExportData(std::unique_ptr &analyzer mLayout.addSeparatorToTopToolbar(); - mSaveSettings = mLayout.addActionButton("Save template", generateIcon("mark-as-favorite")); - connect(mSaveSettings, &QAction::triggered, [this] { saveTemplate(); }); - mOpenSettings = mLayout.addActionButton("Open template", generateIcon("opened-folder")); connect(mOpenSettings, &QAction::triggered, [this] { openTemplate(); }); + mSaveSettings = mLayout.addActionButton("Save template", generateIcon("save")); + connect(mSaveSettings, &QAction::triggered, [this] { saveTemplate(); }); + /* mSelectAllMeasurements = mLayout.addActionButton("Select all measurements", "icons8-select-column); connect(mSelectAllMeasurements, &QAction::triggered, [this] { selectAvgOfAllMeasureChannels(); }); @@ -402,7 +404,16 @@ void DialogExportData::onExportClicked() .exportType = mReportingType->getValue(), .exportDetail = mReportingDetails->getValue(), }; - joda::db::BatchExporter::startExport(settings, filePathOfSettingsFile.toStdString()); + + settings::AnalyzeSettings analyzeSettings; + try { + analyzeSettings = nlohmann::json::parse(mAnalyzeMeta->analyzeSettingsJsonString); + } catch(const std::exception &ex) { + std::cout << "Ups " << ex.what() << std::endl; + } + + joda::db::BatchExporter::startExport(settings, analyzeSettings, mAnalyzeMeta->jobName, mAnalyzeMeta->timestampStart, + mAnalyzeMeta->timestampFinish, filePathOfSettingsFile.toStdString()); emit exportFinished(); }).detach(); } diff --git a/src/ui/results/dialog_export_data.hpp b/src/ui/results/dialog_export_data.hpp index 98d7b0c6..0509f76f 100644 --- a/src/ui/results/dialog_export_data.hpp +++ b/src/ui/results/dialog_export_data.hpp @@ -84,7 +84,7 @@ class DialogExportData : public QDialog public: ///////////////////////////////////////////////////// DialogExportData(std::unique_ptr &analyzer, const db::QueryFilter &filter, - const std::map &clustersAndClasses, QWidget *windowMain); + const std::map &clustersAndClasses, db::AnalyzeMeta *analyzeMeta, QWidget *windowMain); signals: void exportFinished(); @@ -113,6 +113,7 @@ class DialogExportData : public QDialog ///////////////////////////////////////////////////// QWidget *mWindowMain; std::unique_ptr &mAnalyzer; + db::AnalyzeMeta *mAnalyzeMeta = nullptr; const db::QueryFilter &mFilter; std::vector mExportColumns; diff --git a/src/ui/results/panel_results.cpp b/src/ui/results/panel_results.cpp index 8ed9eb46..4d109c82 100644 --- a/src/ui/results/panel_results.cpp +++ b/src/ui/results/panel_results.cpp @@ -157,9 +157,12 @@ void PanelResults::createBreadCrump(joda::ui::helper::LayoutGenerator *toolbar) clustersAndClasses.emplace(SettingComboBoxClassificationUnmanaged::fromInt(mClusterClassSelector->itemData(i).toUInt()), mClusterClassSelector->itemText(i)); } - - DialogExportData exportData(mAnalyzer, mFilter, clustersAndClasses, mWindowMain); - exportData.exec(); + if(mSelectedDataSet.analyzeMeta.has_value()) { + DialogExportData exportData(mAnalyzer, mFilter, clustersAndClasses, &mSelectedDataSet.analyzeMeta.value(), mWindowMain); + exportData.exec(); + } else { + /// \todo Add error message + } }); toolbar->addItemToTopToolbar(exportData); @@ -667,9 +670,8 @@ void PanelResults::openFromFile(const QString &pathToDbFile) mAnalyzer->openDatabase(std::filesystem::path(pathToDbFile.toStdString())); setAnalyzer(); if(mSelectedDataSet.analyzeMeta.has_value()) { - getWindowMain()->getPanelResultsInfo()->addResultsFileToHistory(std::filesystem::path(pathToDbFile.toStdString()), - mSelectedDataSet.analyzeMeta->experiment.experimentName, - mSelectedDataSet.analyzeMeta->timestamp); + getWindowMain()->getPanelResultsInfo()->addResultsFileToHistory( + std::filesystem::path(pathToDbFile.toStdString()), mSelectedDataSet.analyzeMeta->jobName, mSelectedDataSet.analyzeMeta->timestampStart); } } catch(const std::exception &ex) {