@@ -1211,6 +1211,350 @@ void test_missing_illuminant_data()
12111211 output.find ( " Error: No matching light source" ) != std::string::npos );
12121212}
12131213
1214+ // / Tests that conversion fails when specified illuminant type is not found in illuminant data (should fail)
1215+ void test_illuminant_type_not_found ()
1216+ {
1217+ std::cout << std::endl << " test_illuminant_type_not_found()" << std::endl;
1218+
1219+ // Create test directory with database
1220+ TestDirectory test_dir;
1221+
1222+ // Create camera data (so camera lookup succeeds)
1223+ test_dir.create_test_data_file (
1224+ " camera" ,
1225+ { { " manufacturer" , " Blackmagic" }, { " model" , " Cinema Camera" } } );
1226+
1227+ // Create training data (so training data loading succeeds)
1228+ test_dir.create_test_data_file ( " training" , { { " illuminant" , " D65" } } );
1229+
1230+ // Create observer data (so observer data loading succeeds)
1231+ test_dir.create_test_data_file ( " cmf" , { { " illuminant" , " D65" } } );
1232+
1233+ // Create illuminant data with a specific illuminant type (e.g., "D65")
1234+ // but we'll request a different type that doesn't exist
1235+ test_dir.create_test_data_file ( " illuminant" , { { " illuminant" , " D65" } } );
1236+
1237+ // Create a mock ImageSpec with camera metadata
1238+ OIIO::ImageSpec image_spec;
1239+ image_spec.width = 100 ;
1240+ image_spec.height = 100 ;
1241+ image_spec.nchannels = 3 ;
1242+ image_spec.format = OIIO::TypeDesc::UINT8;
1243+ image_spec[" cameraMake" ] = " Blackmagic" ;
1244+ image_spec[" cameraModel" ] = " Cinema Camera" ;
1245+
1246+ // Configure settings with illuminant that doesn't exist in the database
1247+ ImageConverter::Settings settings;
1248+ settings.database_directories = { test_dir.get_database_path () };
1249+ settings.illuminant = " A" ; // Request illuminant "A" which doesn't exist
1250+ settings.verbosity = 1 ;
1251+
1252+ // Test: Request an illuminant type that doesn't exist in the illuminant data
1253+ std::vector<double > WB_multipliers;
1254+ std::vector<std::vector<double >> IDT_matrix;
1255+ std::vector<std::vector<double >> CAT_matrix;
1256+
1257+ // Capture stderr output to verify error messages
1258+ bool success;
1259+ std::string output = capture_stderr ( [&]() {
1260+ // This should fail because illuminant "A" is not found
1261+ success = prepare_transform_spectral (
1262+ image_spec, settings, WB_multipliers, IDT_matrix, CAT_matrix );
1263+ } );
1264+
1265+ // Should fail
1266+ OIIO_CHECK_ASSERT ( !success );
1267+
1268+ // Assert on the expected error message
1269+ OIIO_CHECK_ASSERT (
1270+ output.find ( " Failed to find illuminant type = 'a'." ) !=
1271+ std::string::npos );
1272+ }
1273+
1274+ // / Tests that auto-detection of illuminant works with 4-channel WB_multipliers and verbosity output
1275+ void test_auto_detect_illuminant_with_wb_multipliers ()
1276+ {
1277+ std::cout << std::endl
1278+ << " test_auto_detect_illuminant_with_wb_multipliers()"
1279+ << std::endl;
1280+
1281+ // Create test directory with database
1282+ TestDirectory test_dir;
1283+
1284+ // Create camera data (so camera lookup succeeds)
1285+ test_dir.create_test_data_file (
1286+ " camera" ,
1287+ { { " manufacturer" , " Blackmagic" }, { " model" , " Cinema Camera" } } );
1288+
1289+ // Create training data (so training data loading succeeds)
1290+ test_dir.create_test_data_file ( " training" , { { " illuminant" , " D65" } } );
1291+
1292+ // Create observer data (so observer data loading succeeds)
1293+ test_dir.create_test_data_file ( " cmf" , { { " illuminant" , " D65" } } );
1294+
1295+ // Create illuminant data (so illuminant data loading succeeds)
1296+ test_dir.create_test_data_file ( " illuminant" , { { " illuminant" , " D65" } } );
1297+
1298+ // Create a mock ImageSpec with camera metadata
1299+ OIIO::ImageSpec image_spec;
1300+ image_spec.width = 100 ;
1301+ image_spec.height = 100 ;
1302+ image_spec.nchannels = 3 ;
1303+ image_spec.format = OIIO::TypeDesc::UINT8;
1304+ image_spec[" cameraMake" ] = " Blackmagic" ;
1305+ image_spec[" cameraModel" ] = " Cinema Camera" ;
1306+
1307+ // Configure settings with empty illuminant (to trigger auto-detection)
1308+ // and verbosity > 0 (to trigger the "Found illuminant:" message)
1309+ ImageConverter::Settings settings;
1310+ settings.database_directories = { test_dir.get_database_path () };
1311+ settings.illuminant = " " ; // Empty to trigger auto-detection
1312+ settings.verbosity = 1 ; // > 0 to trigger the output message
1313+
1314+ // Provide WB_multipliers with size 4 to exercise the 4-channel path
1315+ std::vector<double > WB_multipliers = { 1.5 , 1.0 , 1.2 , 1.0 }; // 4 channels
1316+ std::vector<std::vector<double >> IDT_matrix;
1317+ std::vector<std::vector<double >> CAT_matrix;
1318+
1319+ bool success;
1320+ std::string output = capture_stderr ( [&]() {
1321+ // This should succeed and auto-detect the illuminant
1322+ // This will exercise the 4-channel WB_multipliers path (when WB_multipliers.size() == 4)
1323+ // and the verbosity output path (when verbosity > 0 and illuminant is found)
1324+ success = prepare_transform_spectral (
1325+ image_spec, settings, WB_multipliers, IDT_matrix, CAT_matrix );
1326+ } );
1327+
1328+ // Should succeed
1329+ OIIO_CHECK_ASSERT ( success );
1330+
1331+ // With the current mocked input (WB_multipliers = {1.5, 1.0, 1.2, 1.0}),
1332+ OIIO_CHECK_ASSERT (
1333+ output.find ( " Found illuminant: '2000k'." ) != std::string::npos );
1334+ }
1335+
1336+ // / Tests that auto-detection extracts white balance from RAW metadata when WB_multipliers is not provided
1337+ void test_auto_detect_illuminant_from_raw_metadata ()
1338+ {
1339+ std::cout << std::endl
1340+ << " test_auto_detect_illuminant_from_raw_metadata()" << std::endl;
1341+
1342+ // Create test directory with database
1343+ TestDirectory test_dir;
1344+
1345+ // Create camera data (so camera lookup succeeds)
1346+ test_dir.create_test_data_file (
1347+ " camera" ,
1348+ { { " manufacturer" , " Blackmagic" }, { " model" , " Cinema Camera" } } );
1349+
1350+ // Create training data (so training data loading succeeds)
1351+ test_dir.create_test_data_file ( " training" , { { " illuminant" , " D65" } } );
1352+
1353+ // Create observer data (so observer data loading succeeds)
1354+ test_dir.create_test_data_file ( " cmf" , { { " illuminant" , " D65" } } );
1355+
1356+ // Create illuminant data (so illuminant data loading succeeds)
1357+ test_dir.create_test_data_file ( " illuminant" , { { " illuminant" , " D65" } } );
1358+
1359+ // Use direct library method to control WB_multipliers and force the else path
1360+ // The exec method populates WB_multipliers from metadata, so it takes the if branch.
1361+ // To test the else branch (extract from raw:pre_mul), we need WB_multipliers.size() != 4.
1362+
1363+ // Create a mock ImageSpec with camera metadata and raw:pre_mul attribute
1364+ OIIO::ImageSpec image_spec;
1365+ image_spec.width = 100 ;
1366+ image_spec.height = 100 ;
1367+ image_spec.nchannels = 3 ;
1368+ image_spec.format = OIIO::TypeDesc::UINT8;
1369+ image_spec[" cameraMake" ] = " Blackmagic" ;
1370+ image_spec[" cameraModel" ] = " Cinema Camera" ;
1371+
1372+ // Add raw:pre_mul attribute to simulate RAW metadata extraction path
1373+ float pre_mul[4 ] = { 1 .5f , 1 .0f , 1 .2f , 1 .0f };
1374+ image_spec.attribute (
1375+ " raw:pre_mul" , OIIO::TypeDesc ( OIIO::TypeDesc::FLOAT, 4 ), pre_mul );
1376+
1377+ // Configure settings with empty illuminant (to trigger auto-detection)
1378+ // and verbosity > 0 (to trigger the "Found illuminant:" message)
1379+ ImageConverter::Settings settings;
1380+ settings.database_directories = { test_dir.get_database_path () };
1381+ settings.illuminant = " " ; // Empty to trigger auto-detection
1382+ settings.verbosity = 1 ; // > 0 to trigger the output message
1383+
1384+ // Provide empty WB_multipliers to trigger extraction from raw:pre_mul
1385+ // This exercises the path where WB_multipliers.size() != 4
1386+ std::vector<double >
1387+ WB_multipliers; // Empty - will trigger raw:pre_mul extraction
1388+ std::vector<std::vector<double >> IDT_matrix;
1389+ std::vector<std::vector<double >> CAT_matrix;
1390+
1391+ bool success;
1392+ std::string output = capture_stderr ( [&]() {
1393+ // This should succeed and auto-detect the illuminant from raw:pre_mul
1394+ // This exercises the extraction path when WB_multipliers is not provided
1395+ success = prepare_transform_spectral (
1396+ image_spec, settings, WB_multipliers, IDT_matrix, CAT_matrix );
1397+ } );
1398+
1399+ // Should succeed
1400+ OIIO_CHECK_ASSERT ( success );
1401+
1402+ // Verify the "Found illuminant:" message appears
1403+ OIIO_CHECK_ASSERT (
1404+ output.find ( " Found illuminant: '2000k'." ) != std::string::npos );
1405+ }
1406+
1407+ // / Tests that auto-detection normalizes white balance multipliers when min_val > 0 and != 1
1408+ void test_auto_detect_illuminant_with_normalization ()
1409+ {
1410+ std::cout << std::endl
1411+ << " test_auto_detect_illuminant_with_normalization()"
1412+ << std::endl;
1413+
1414+ // Create test directory with database
1415+ TestDirectory test_dir;
1416+
1417+ // Create camera data (so camera lookup succeeds)
1418+ test_dir.create_test_data_file (
1419+ " camera" ,
1420+ { { " manufacturer" , " Blackmagic" }, { " model" , " Cinema Camera" } } );
1421+
1422+ // Create training data (so training data loading succeeds)
1423+ test_dir.create_test_data_file ( " training" , { { " illuminant" , " D65" } } );
1424+
1425+ // Create observer data (so observer data loading succeeds)
1426+ test_dir.create_test_data_file ( " cmf" , { { " illuminant" , " D65" } } );
1427+
1428+ // Create illuminant data (so illuminant data loading succeeds)
1429+ test_dir.create_test_data_file ( " illuminant" , { { " illuminant" , " D65" } } );
1430+
1431+ // Create a mock ImageSpec with camera metadata and raw:pre_mul attribute
1432+ OIIO::ImageSpec image_spec;
1433+ image_spec.width = 100 ;
1434+ image_spec.height = 100 ;
1435+ image_spec.nchannels = 3 ;
1436+ image_spec.format = OIIO::TypeDesc::UINT8;
1437+ image_spec[" cameraMake" ] = " Blackmagic" ;
1438+ image_spec[" cameraModel" ] = " Cinema Camera" ;
1439+
1440+ // Add raw:pre_mul attribute with values where min_val > 0 and != 1
1441+ // Using values like {2.0, 1.5, 1.8, 1.5} where min=1.5, which is > 0 and != 1
1442+ // This will trigger the normalization path
1443+ float pre_mul[4 ] = { 2 .0f , 1 .5f , 1 .8f , 1 .5f };
1444+ image_spec.attribute (
1445+ " raw:pre_mul" , OIIO::TypeDesc ( OIIO::TypeDesc::FLOAT, 4 ), pre_mul );
1446+
1447+ // Configure settings with empty illuminant (to trigger auto-detection)
1448+ // and verbosity > 0 (to trigger the "Found illuminant:" message)
1449+ ImageConverter::Settings settings;
1450+ settings.database_directories = { test_dir.get_database_path () };
1451+ settings.illuminant = " " ; // Empty to trigger auto-detection
1452+ settings.verbosity = 1 ; // > 0 to trigger the output message
1453+
1454+ // Provide empty WB_multipliers to trigger extraction from raw:pre_mul
1455+ // This exercises the path where WB_multipliers.size() != 4
1456+ std::vector<double >
1457+ WB_multipliers; // Empty - will trigger raw:pre_mul extraction
1458+ std::vector<std::vector<double >> IDT_matrix;
1459+ std::vector<std::vector<double >> CAT_matrix;
1460+
1461+ bool success;
1462+ std::string output = capture_stderr ( [&]() {
1463+ // This should succeed and auto-detect the illuminant from raw:pre_mul
1464+ // The normalization path will be exercised when min_val > 0 and != 1
1465+ success = prepare_transform_spectral (
1466+ image_spec, settings, WB_multipliers, IDT_matrix, CAT_matrix );
1467+ } );
1468+
1469+ // Should succeed
1470+ OIIO_CHECK_ASSERT ( success );
1471+
1472+ // Verify the "Found illuminant:" message appears
1473+ OIIO_CHECK_ASSERT (
1474+ output.find ( " Found illuminant: '1500k'." ) != std::string::npos );
1475+ }
1476+
1477+ // / Tests that prepare_transform_spectral fails when IDT matrix calculation fails
1478+ void test_prepare_transform_spectral_idt_calculation_fail ()
1479+ {
1480+ std::cout << std::endl
1481+ << " test_prepare_transform_spectral_idt_calculation_fail()"
1482+ << std::endl;
1483+
1484+ // Create test directory with database
1485+ TestDirectory test_dir;
1486+
1487+ // Create camera data (so camera lookup succeeds)
1488+ test_dir.create_test_data_file (
1489+ " camera" ,
1490+ { { " manufacturer" , " Blackmagic" }, { " model" , " Cinema Camera" } } );
1491+
1492+ // Create observer data (so observer data loading succeeds)
1493+ test_dir.create_test_data_file ( " cmf" , { { " illuminant" , " D65" } } );
1494+
1495+ // Create illuminant data (so illuminant data loading succeeds)
1496+ test_dir.create_test_data_file ( " illuminant" , { { " illuminant" , " D65" } } );
1497+
1498+ // Create training data with minimal structure that causes curve fitting to fail
1499+ // We need to create a file that loads but causes optimization to fail
1500+ std::string training_dir = test_dir.get_database_path () + " /training" ;
1501+ std::filesystem::create_directories ( training_dir );
1502+ std::string training_file = training_dir + " /training_spectral.json" ;
1503+
1504+ // Create training data with only one patch and minimal wavelengths
1505+ // This should pass initial validation but cause curve fitting to fail
1506+ nlohmann::json training_json;
1507+ training_json[" header" ][" illuminant" ] = " D65" ;
1508+ training_json[" units" ] = " relative" ;
1509+ training_json[" index" ] = { { " main" , { " patch1" } } };
1510+
1511+ nlohmann::json data_main;
1512+ // Add only a few wavelengths - insufficient for proper curve fitting
1513+ data_main[" 380" ] = { 0.1 };
1514+ data_main[" 385" ] = { 0.1 };
1515+ data_main[" 390" ] = { 0.1 };
1516+ training_json[" data" ][" main" ] = data_main;
1517+
1518+ std::ofstream training_out ( training_file );
1519+ training_out << training_json.dump ( 4 );
1520+ training_out.close ();
1521+
1522+ // Create a mock ImageSpec with camera metadata
1523+ OIIO::ImageSpec image_spec;
1524+ image_spec.width = 100 ;
1525+ image_spec.height = 100 ;
1526+ image_spec.nchannels = 3 ;
1527+ image_spec.format = OIIO::TypeDesc::UINT8;
1528+ image_spec[" cameraMake" ] = " Blackmagic" ;
1529+ image_spec[" cameraModel" ] = " Cinema Camera" ;
1530+
1531+ // Configure settings with illuminant specified
1532+ ImageConverter::Settings settings;
1533+ settings.database_directories = { test_dir.get_database_path () };
1534+ settings.illuminant = " D65" ;
1535+ settings.verbosity = 1 ;
1536+
1537+ // Provide WB_multipliers
1538+ std::vector<double > WB_multipliers = { 1.5 , 1.0 , 1.2 };
1539+ std::vector<std::vector<double >> IDT_matrix;
1540+ std::vector<std::vector<double >> CAT_matrix;
1541+
1542+ bool success;
1543+ std::string output = capture_stderr ( [&]() {
1544+ // This should fail when trying to calculate IDT matrix
1545+ success = prepare_transform_spectral (
1546+ image_spec, settings, WB_multipliers, IDT_matrix, CAT_matrix );
1547+ } );
1548+
1549+ // Should fail
1550+ OIIO_CHECK_ASSERT ( !success );
1551+
1552+ // Verify the error message about failed IDT matrix calculation
1553+ OIIO_CHECK_ASSERT (
1554+ output.find ( " Failed to calculate the input transform matrix." ) !=
1555+ std::string::npos );
1556+ }
1557+
12141558void assert_success_conversion ( const std::string &output )
12151559{
12161560 // Assert that the command succeeded (no error messages)
@@ -1587,12 +1931,17 @@ int main( int, char ** )
15871931 test_missing_training_data ();
15881932 test_missing_observer_data ();
15891933 test_missing_illuminant_data ();
1934+ test_illuminant_type_not_found ();
1935+ test_auto_detect_illuminant_with_wb_multipliers ();
1936+ test_auto_detect_illuminant_from_raw_metadata ();
1937+ test_auto_detect_illuminant_with_normalization ();
15901938
15911939 test_spectral_conversion_success ();
15921940 test_rawtoaces_spectral_mode_complete_success_with_custom_camera_info ();
15931941
15941942 test_prepare_transform_spectral_wb_calculation_fail_due_to_invalid_illuminant_data ();
15951943 test_prepare_transform_spectral_wb_calculation_fail_due_to_invalid_camera_data ();
1944+ test_prepare_transform_spectral_idt_calculation_fail ();
15961945
15971946 test_rawtoaces_spectral_mode_complete_success_with_default_illuminant_warning ();
15981947 test_illuminant_ignored_with_metadata_wb ();
0 commit comments