-
Notifications
You must be signed in to change notification settings - Fork 63
[feat] implement aliases #262
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,6 +2,10 @@ | |
| // Copyright Contributors to the rawtoaces Project. | ||
|
|
||
| #include <rawtoaces/rawtoaces_core.h> | ||
|
|
||
| #include <nlohmann/json.hpp> | ||
| #include <fstream> | ||
|
|
||
| #include "rawtoaces_core_priv.h" | ||
| #include "mathOps.h" | ||
| #include "define.h" | ||
|
|
@@ -266,6 +270,109 @@ SpectralSolver::collect_data_files( const std::string &type ) const | |
| return result; | ||
| } | ||
|
|
||
| bool SpectralSolver::collect_aliases( | ||
| const std::string &type, | ||
| std::map<std::string, std::string> &make_aliases, | ||
| std::map< | ||
| std::string, | ||
| std::map<std::string, std::pair<std::string, std::string>>> | ||
| &model_aliases ) const | ||
| { | ||
| for ( const auto &directory: _search_directories ) | ||
| { | ||
| std::filesystem::path path( directory ); | ||
| path.append( "aliases.json" ); | ||
|
|
||
| if ( std::filesystem::exists( path ) ) | ||
| { | ||
| // For now we consider the aliases files optional. | ||
| // In a case of error, we just skip the file and process the next | ||
| // one. We still want to report warnings to let the user a chance | ||
| // to fix broken files. | ||
|
|
||
| std::ifstream ifstream( path ); | ||
| if ( !ifstream.is_open() ) | ||
| { | ||
| std::cerr << "Warning: cannot open file " << path.string() | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we do show warning when you can't open the path, but we don't dow it for when it doesn't exist in a first place. expected?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Expected. This path is actually the only bit which is not covered in unit tests. Same with the spectral files. May want to come up with a way to test if an existing file can't be opened, like restricting file permissions, or something. Will leave this for later, as it is not directly related to this feature. |
||
| << "." << std::endl; | ||
| continue; | ||
| } | ||
|
|
||
| nlohmann::json file_data; | ||
|
|
||
| try | ||
| { | ||
| file_data = nlohmann::json::parse( ifstream ); | ||
| } | ||
| catch ( const std::exception &error ) | ||
| { | ||
| std::cerr << "Warning: JSON parsing of the alias file " | ||
| << path.string() | ||
| << " failed with error: " << error.what() << "." | ||
| << std::endl; | ||
| continue; | ||
| } | ||
|
|
||
| if ( !file_data.contains( "data" ) ) | ||
| { | ||
| std::cerr << "Warning: the alias file " << path.string() | ||
| << " must contain the 'data' section." << std::endl; | ||
| continue; | ||
| } | ||
|
|
||
| const auto &mapping_data = file_data["data"]; | ||
|
|
||
| if ( !mapping_data.contains( type ) ) | ||
| { | ||
| continue; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. for some "skip" we show warning, for others we don't
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The section is optional, being omitted from an aliases files is a normal case. We just go to the next file. |
||
| } | ||
|
|
||
| const auto &type_mapping = mapping_data[type]; | ||
|
|
||
| if ( type_mapping.contains( "make" ) ) | ||
| { | ||
| const auto &make_mapping = type_mapping["make"]; | ||
| for ( auto &[src_make, dst_make]: make_mapping.items() ) | ||
| { | ||
| if ( make_aliases.find( src_make ) == make_aliases.end() ) | ||
| { | ||
| make_aliases[src_make] = dst_make; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| if ( type_mapping.contains( "model" ) ) | ||
| { | ||
| const auto &model_mapping = type_mapping["model"]; | ||
| for ( auto &[src_make, models]: model_mapping.items() ) | ||
| { | ||
| for ( auto &[src_model, dst]: models.items() ) | ||
| { | ||
| const std::string src_make_str = src_make; | ||
| const std::string src_model_str = src_model; | ||
| if ( model_aliases.find( src_make_str ) != | ||
| model_aliases.end() && | ||
| model_aliases[src_make_str].find( | ||
| src_model_str ) != | ||
| model_aliases[src_make_str].end() ) | ||
| { | ||
| continue; | ||
| } | ||
|
|
||
| const std::string dst_make_str = dst["make"]; | ||
| const std::string dst_model_str = dst["model"]; | ||
| model_aliases[src_make_str][src_model_str] = { | ||
| dst_make_str, dst_model_str | ||
| }; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| bool SpectralSolver::load_spectral_data( | ||
| const std::string &file_path, SpectralData &out_data ) | ||
| { | ||
|
|
@@ -304,15 +411,69 @@ bool SpectralSolver::find_camera( | |
| return true; | ||
| } | ||
|
|
||
| std::string dst_make = make; | ||
| std::string dst_model = model; | ||
|
|
||
| std::map<std::string, std::string> make_aliases; | ||
| std::map< | ||
| std::string, | ||
| std::map<std::string, std::pair<std::string, std::string>>> | ||
| model_aliases; | ||
|
|
||
| if ( collect_aliases( "camera", make_aliases, model_aliases ) ) | ||
| { | ||
|
|
||
| bool found_make_alias = false; | ||
| for ( const auto &[alias_src_make, alias_dst_make]: make_aliases ) | ||
| { | ||
| if ( is_not_equal_insensitive( make, alias_src_make ) ) | ||
| continue; | ||
|
|
||
| dst_make = alias_dst_make; | ||
| found_make_alias = true; | ||
| break; | ||
| } | ||
|
|
||
| bool found_model_alias = false; | ||
| for ( const auto &[alias_src_make, alias_src_dict]: model_aliases ) | ||
| { | ||
| if ( is_not_equal_insensitive( dst_make, alias_src_make ) ) | ||
| continue; | ||
|
|
||
| for ( const auto &[alias_src_model, alias_dst_pair]: | ||
| alias_src_dict ) | ||
| { | ||
| if ( is_not_equal_insensitive( model, alias_src_model ) ) | ||
| continue; | ||
| dst_make = alias_dst_pair.first; | ||
| dst_model = alias_dst_pair.second; | ||
| found_model_alias = true; | ||
| break; | ||
| } | ||
|
|
||
| if ( found_model_alias ) | ||
| break; | ||
| } | ||
|
|
||
| if ( found_make_alias || found_model_alias ) | ||
| { | ||
| if ( !is_not_equal_insensitive( camera.manufacturer, dst_make ) && | ||
| !is_not_equal_insensitive( camera.model, dst_model ) ) | ||
| { | ||
| return true; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| auto camera_files = collect_data_files( "camera" ); | ||
|
|
||
| for ( const auto &camera_file: camera_files ) | ||
| { | ||
| camera.load( camera_file ); | ||
|
|
||
| if ( is_not_equal_insensitive( camera.manufacturer, make ) ) | ||
| if ( is_not_equal_insensitive( camera.manufacturer, dst_make ) ) | ||
| continue; | ||
| if ( is_not_equal_insensitive( camera.model, model ) ) | ||
| if ( is_not_equal_insensitive( camera.model, dst_model ) ) | ||
| continue; | ||
| return true; | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1409,6 +1409,43 @@ std::vector<std::string> ImageConverter::get_supported_illuminants() const | |
| return result; | ||
| } | ||
|
|
||
| /// Combine the make and model strings together, unless they are the same, | ||
| /// or the model name starts with the make followed by a whitespace. | ||
| /// "Canon" + "Canon R5" = "Canon R5" | ||
| /// "Canon R5" + "Canon R5" = "Canon R5" | ||
| /// "Canon" + "Canon_R5" = "Canon Canon_R5" | ||
| std::string | ||
| combine_make_model( const std::string &make, const std::string &model ) | ||
| { | ||
| bool has_prefix = true; | ||
|
|
||
| if ( make.length() <= model.length() ) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am very confused about this logic. If it is correct we should leave comment here.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is trivial really. Skip the prefix if it matches the make name, or the make name + whitespace. |
||
| { | ||
| size_t i; | ||
| for ( i = 0; i < make.length(); i++ ) | ||
| { | ||
| if ( std::tolower( make[i] ) != std::tolower( model[i] ) ) | ||
| { | ||
| has_prefix = false; | ||
| break; | ||
| } | ||
| } | ||
| if ( has_prefix && ( i < model.length() ) ) | ||
| { | ||
| has_prefix = model[i] == ' '; | ||
| } | ||
| } | ||
| else | ||
| { | ||
| has_prefix = false; | ||
| } | ||
|
|
||
| if ( has_prefix ) | ||
| return model; | ||
| else | ||
| return make + " " + model; | ||
| } | ||
|
|
||
| std::vector<std::string> ImageConverter::get_supported_cameras() const | ||
| { | ||
| std::vector<std::string> result; | ||
|
|
@@ -1420,11 +1457,54 @@ std::vector<std::string> ImageConverter::get_supported_cameras() const | |
| core::SpectralData data; | ||
| if ( data.load( file, false ) ) | ||
| { | ||
| std::string name = data.manufacturer + " / " + data.model; | ||
| std::string name = | ||
| combine_make_model( data.manufacturer, data.model ); | ||
| result.push_back( name ); | ||
| } | ||
| } | ||
|
|
||
| std::map<std::string, std::string> make_aliases; | ||
| std::map< | ||
| std::string, | ||
| std::map<std::string, std::pair<std::string, std::string>>> | ||
| model_aliases; | ||
| if ( solver.collect_aliases( "camera", make_aliases, model_aliases ) ) | ||
| { | ||
| std::vector<std::string> parsed_aliases; | ||
| for ( auto &[src_make, src_models]: model_aliases ) | ||
| { | ||
| for ( auto &[src_model, dst]: src_models ) | ||
| { | ||
| const std::string &dst_make = dst.first; | ||
| const std::string &dst_model = dst.second; | ||
| const std::string dst_make_model = | ||
| combine_make_model( dst_make, dst_model ); | ||
| const std::string src_make_model = | ||
| combine_make_model( src_make, src_model ); | ||
|
|
||
| if ( std::find( | ||
| result.begin(), result.end(), dst_make_model ) != | ||
| result.end() ) | ||
| { | ||
| const std::string entry = | ||
| src_make_model + " (alias for " + dst_make_model + ")"; | ||
| parsed_aliases.push_back( entry ); | ||
| } | ||
| else | ||
| { | ||
| std::cerr << "Warning: the alias " << src_make_model | ||
| << " points to a missing camera " | ||
| << dst_make_model << std::endl; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| result.insert( | ||
| result.end(), parsed_aliases.begin(), parsed_aliases.end() ); | ||
| } | ||
|
|
||
| std::sort( result.begin(), result.end() ); | ||
|
|
||
| return result; | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
returning
truewhen path doesn't exist is expected?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is an optional file, it is not expected to be in every directory specified in the paths list.