diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ccde1960..fc99859f 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -21,6 +21,8 @@ add_executable ( Test_usage_example_core usage_example_core.cpp test_utils.cpp + test_data_helpers.cpp + test_data_helpers.h ) target_link_libraries( @@ -39,6 +41,8 @@ add_test ( NAME Test_usage_example_core COMMAND Test_usage_example_core ) add_executable ( Test_usage_example_util usage_example_util.cpp + test_data_helpers.cpp + test_data_helpers.h ) target_link_libraries( @@ -46,6 +50,8 @@ target_link_libraries( PUBLIC ${RAWTOACES_UTIL_LIB} OpenImageIO::OpenImageIO + PRIVATE + nlohmann_json::nlohmann_json ) setup_test_coverage(Test_usage_example_util) @@ -56,6 +62,8 @@ add_executable ( Test_SpectralData test_SpectralData.cpp test_utils.cpp + test_data_helpers.cpp + test_data_helpers.h ) target_link_libraries( @@ -76,7 +84,9 @@ add_executable ( Test_IDT testIDT.cpp test_utils.cpp - ../src/misc/pragma.h + test_data_helpers.cpp + test_data_helpers.h + ../src/misc/pragma.h ) target_link_libraries( @@ -114,7 +124,9 @@ add_executable ( Test_DNGIdt testDNGIdt.cpp test_utils.cpp - ../src/misc/pragma.h + test_data_helpers.cpp + test_data_helpers.h + ../src/misc/pragma.h ) target_link_libraries( @@ -186,6 +198,8 @@ add_executable ( Test_UsageTimer testUsageTimer.cpp test_utils.cpp + test_data_helpers.cpp + test_data_helpers.h ) target_link_libraries( @@ -206,6 +220,8 @@ add_executable ( Test_Cache testCache.cpp test_utils.cpp + test_data_helpers.cpp + test_data_helpers.h ) target_link_libraries( @@ -226,6 +242,8 @@ add_executable ( Test_Exiftool testExiftool.cpp test_utils.cpp + test_data_helpers.cpp + test_data_helpers.h ) target_link_libraries( @@ -248,6 +266,8 @@ if ( RTA_ENABLE_LENSFUN ) Test_LensCorrection testLensCorrection.cpp test_utils.cpp + test_data_helpers.cpp + test_data_helpers.h ) target_link_libraries( @@ -270,6 +290,8 @@ add_executable ( Test_ColourTransforms testColourTransforms.cpp test_utils.cpp + test_data_helpers.cpp + test_data_helpers.h ) target_link_libraries( @@ -291,6 +313,8 @@ add_executable ( test_image_converter.cpp test_utils.cpp test_utils_image_converter.cpp + test_data_helpers.cpp + test_data_helpers.h ) target_link_libraries( diff --git a/tests/test_data_helpers.cpp b/tests/test_data_helpers.cpp new file mode 100644 index 00000000..f7449cfa --- /dev/null +++ b/tests/test_data_helpers.cpp @@ -0,0 +1,195 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Contributors to the rawtoaces Project. + +#include "test_data_helpers.h" + +#include +#include + +// Parametric spectral curve in the form of Gaussian function with asymmetric +// shape: f(x) = a * exp( - (x - b)^2 / 2c^2 ) ) +// Where: +// a - maximum value +// b - wavelength at peak +// c - shape, c = c1 to the left of the peak, c2 otherwise. +// HWHM is roughly 1.177 * c +struct SpectralCurve +{ + const char *name; + float a; + float b; + float c1; + float c2; +}; + +struct SpectralCurve HypotheticalCamera[] = { + { "R", 0.7f, 600.0f, 50.0f, 40.0f }, + { "G", 1.0f, 520.0f, 40.0f, 50.0f }, + { "B", 0.9f, 450.0f, 20.0f, 60.0f } +}; + +struct SpectralCurve HypotheticalIlluminant[] = { + { "power", 102.17f, 479.5f, 69.2f, 333.8f } +}; + +struct SpectralCurve HypotheticalObserver[] = { + { "X", 1.06f, 599.0f, 36.0f, 31.4f }, + { "Y", 1.02f, 553.0f, 36.4f, 47.1f }, + { "Z", 1.86f, 444.1f, 16.9f, 28.6f } +}; + +struct SpectralCurve HypotheticalTrainingData[] = { + { "dark skin", 0.23f, 853.2f, 243.3f, 868.5f }, + { "light skin", 0.84f, 876.7f, 254.5f, 935.0f }, + { "blue sky", 0.34f, 152.7f, 686.5f, 409.3f }, + { "foliage", 0.12f, 564.2f, 108.6f, 2765.7f }, + { "blue flower", 0.86f, -179871.6f, 281557.4f, 127679.8f }, + { "bluish green", 0.48f, 472.6f, 58.8f, 176.6f }, + { "orange", 0.64f, 636.9f, 63.0f, 25075.8f }, + { "purplish blue", 0.21f, -78.6f, 1605.9f, 6790.9f }, + { "moderate red", 0.61f, 646.9f, 48.2f, 8427.9f }, + { "purple", 2.67f, -2277037.9f, 3551360.2f, 991385.8f }, + { "yellow green", 0.44f, 531.5f, 37.1f, 323.0f }, + { "orange yellow", 0.67f, 617.9f, 67.3f, 28865.4f }, + { "blue", 0.30f, 450.5f, 35.5f, 42.1f }, + { "green", 0.25f, 512.2f, 36.0f, 137.9f }, + { "red", 0.72f, 655.4f, 35.3f, 17634.9f }, + { "yellow", 0.77f, 580.8f, 55.0f, 27054.0f }, + { "magenta", 0.54f, -597175.3f, 1237915.0f, 989153.3f }, + { "cyan", 0.42f, 474.6f, 57.0f, 67.8f }, + { "white 9.5 (.05 D)", 0.92f, 434.8f, 28.9f, 67276.6f }, + { "neutral 8 (.23 D)", 0.59f, 427.0f, 28.0f, 1079.5f }, + { "neutral 6.5 (.44 D)", 0.36f, 424.8f, 31.9f, 760.2f }, + { "neutral 5 (.70 D)", 0.19f, 426.6f, 42.1f, 645.7f }, + { "neutral 3.5 (1.05 D)", 0.09f, 532.9f, 337.3f, 431.0f }, + { "black 2 (1.5 D)", 0.03f, 1025.5f, 8281.3f, 10129.4f } +}; + +template +rta::core::SpectralData generate_spectral_data( + const struct SpectralCurve ( &curves )[size], + const std::string &manufacturer = "", + const std::string &model = "", + const std::string &type = "" ) +{ + rta::core::SpectralData result; + result.manufacturer = manufacturer; + result.model = model; + result.type = type; + result.license = "Apache-2.0"; + result.document_creator = "rawtoaces"; + result.creation_date = "2026-01-01T00:00:00Z"; + result.description = "Hypothetical spectral curves for testing purposes"; + + result.data.emplace( "main", rta::core::SpectralData::SpectralSet() ); + + rta::core::Spectrum::Shape shape = rta::core::Spectrum::ReferenceShape; + + for ( size_t i = 0; i < size; i++ ) + { + auto channel_name = curves[i].name; + auto &channel = result.data["main"].emplace_back( + rta::core::SpectralData::SpectralChannel( + channel_name, rta::core::Spectrum( 0, shape ) ) ); + + float a = curves[i].a; + float b = curves[i].b; + float c1 = curves[i].c1; + float c2 = curves[i].c2; + + size_t index = 0; + for ( float wl = shape.first; wl <= shape.last; wl += shape.step ) + { + float c = wl < b ? c1 : c2; + float value = a * expf( -powf( wl - b, 2.0f ) / 2.0f / c / c ); + channel.second.values[index] = value; + index++; + } + } + + return result; +} + +rta::core::SpectralData +create_hypothetical_camera( const std::string &make, const std::string &model ) +{ + return generate_spectral_data( HypotheticalCamera, make, model, "" ); +} + +rta::core::SpectralData +create_hypothetical_illuminant( const std::string &type ) +{ + return generate_spectral_data( HypotheticalIlluminant, "", "", type ); +} + +rta::core::SpectralData create_hypothetical_observer() +{ + return generate_spectral_data( HypotheticalObserver ); +} + +rta::core::SpectralData create_hypothetical_training_data() +{ + return generate_spectral_data( HypotheticalTrainingData ); +} + +void save_spectral_json( + const rta::core::SpectralData &data, const std::string &filename ) +{ + nlohmann::json json_header; + + json_header["schema_version"] = "1.0.0"; + json_header["description"] = data.description; + json_header["document_creator"] = data.document_creator; + json_header["document_creation_date"] = data.creation_date; + json_header["license"] = data.license; + + if ( !data.manufacturer.empty() ) + json_header["manufacturer"] = data.manufacturer; + + if ( !data.model.empty() ) + json_header["model"] = data.model; + + if ( !data.type.empty() ) + json_header["type"] = data.type; + + nlohmann::json json_data; + json_data["header"] = json_header; + + for ( const auto &set_item: data.data ) + { + const auto &set_name = set_item.first; + const auto &set_data = set_item.second; + + std::vector channels; + for ( const auto &item: set_data ) + { + channels.push_back( item.first ); + } + + assert( !set_data.empty() ); + + nlohmann::json json_spectral_data; + + const auto &shape = set_data[0].second.shape; + + size_t index = 0; + for ( float wl = shape.first; wl <= shape.last; wl += shape.step ) + { + std::vector values( channels.size() ); + + for ( size_t channel = 0; channel < channels.size(); channel++ ) + { + values[channel] = set_data[channel].second.values[index]; + } + + json_spectral_data[std::to_string( int( wl ) )] = values; + index++; + } + json_data["spectral_data"]["data"][set_name] = json_spectral_data; + json_data["spectral_data"]["index"][set_name] = channels; + json_data["spectral_data"]["units"] = "relative"; + } + + std::ofstream file( filename ); + file << json_data.dump( 4 ); +} diff --git a/tests/test_data_helpers.h b/tests/test_data_helpers.h new file mode 100644 index 00000000..7f5f11d2 --- /dev/null +++ b/tests/test_data_helpers.h @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Contributors to the rawtoaces Project. + +#pragma once + +#include + +// The make and model parameters only affect the metadata, not the curves shape. +rta::core::SpectralData create_hypothetical_camera( + const std::string &make = "TEST_CAMERA_MAKE", + const std::string &model = "TEST_CAMERA_MODEL" ); + +// The type parameter only affects the metadata, not the curve shape. +rta::core::SpectralData +create_hypothetical_illuminant( const std::string &type = "TEST_ILLUMINANT" ); + +rta::core::SpectralData create_hypothetical_observer(); + +rta::core::SpectralData create_hypothetical_training_data(); + +void save_spectral_json( + const rta::core::SpectralData &data, const std::string &filename ); diff --git a/tests/test_image_converter.cpp b/tests/test_image_converter.cpp index 8f228601..1b342506 100644 --- a/tests/test_image_converter.cpp +++ b/tests/test_image_converter.cpp @@ -1440,7 +1440,7 @@ void test_auto_detect_illuminant_with_wb_multipliers() "", { "Warning: Directory '", "illuminant' does not exist.", - "Found illuminant: '2000k'." } ); + "Found illuminant: 'd60'." } ); } /// Tests that a warning is issued when a database location path points to a file instead of a directory @@ -2038,7 +2038,7 @@ void test_auto_detect_illuminant_from_raw_metadata() WB_multipliers, true, "", - { "Found illuminant: '2000k'." } ); + { "Found illuminant: 'd60'." } ); } /// Tests that auto-detection normalizes white balance multipliers when min_val > 0 and != 1 @@ -2077,7 +2077,7 @@ void test_auto_detect_illuminant_with_normalization() WB_multipliers, true, "", - { "Found illuminant: '1500k'." } ); + { "Found illuminant: 'd55'." } ); } /// Tests that prepare_transform_spectral fails when IDT matrix calculation fails diff --git a/tests/test_utils.cpp b/tests/test_utils.cpp index fff3b764..44fbaf59 100644 --- a/tests/test_utils.cpp +++ b/tests/test_utils.cpp @@ -15,6 +15,8 @@ #include #include +#include "test_data_helpers.h" + /// RAII helper class to capture stderr output for testing class StderrCapture { @@ -224,6 +226,46 @@ std::string TestDirectory::create_test_data_file( } std::string file_path = target_dir + "/" + filename; + if ( !index_main_override.has_value() && !data_main_override.has_value() ) + { + rta::core::SpectralData data; + + if ( type == "training" ) + { + data = create_hypothetical_training_data(); + save_spectral_json( data, file_path ); + return file_path; + } + else if ( type == "cmf" ) + { + data = create_hypothetical_observer(); + save_spectral_json( data, file_path ); + return file_path; + } + else if ( type == "camera" ) + { + if ( header_data.contains( "manufacturer" ) && + header_data.contains( "model" ) ) + { + auto make = header_data["manufacturer"]; + auto model = header_data["model"]; + data = create_hypothetical_camera( make, model ); + save_spectral_json( data, file_path ); + return file_path; + } + } + else if ( type == "illuminant" ) + { + if ( header_data.contains( "type" ) ) + { + auto illuminant_type = header_data["type"]; + data = create_hypothetical_illuminant( illuminant_type ); + save_spectral_json( data, file_path ); + return file_path; + } + } + } + // Create minimal JSON structure with only what's actually used nlohmann::json json_data;