diff --git a/src/fmri/Options.cpp b/src/fmri/Options.cpp index 459ea97..d04d5a9 100644 --- a/src/fmri/Options.cpp +++ b/src/fmri/Options.cpp @@ -2,16 +2,15 @@ #include #include #include +#include #include "Options.hpp" -#include "utils.hpp" using namespace fmri; -using namespace std; -static void show_help(const char *progname, int exitcode) { - cerr << "Usage: " << progname << " -m MODEL -w WEIGHTS INPUTS..." << endl - << endl - << R"END(Simulate the specified network on the specified inputs. +[[noreturn]] static void show_help(const char *progname, int exitcode) +{ + std::cerr << "Usage: " << progname << " -m MODEL -w WEIGHTS INPUTS...\n\n" + << R"END(Simulate the specified network on the specified inputs. Options: -h show this message @@ -19,129 +18,161 @@ Options: -w (required) the trained weights -m means file. Will be substracted from input if available. -l labels file. Will be used to print prediction labels if available. - -d Image dump dir. Will be filled with PNG images of intermediate results.)END" << endl; + -d Image dump dir. Will be filled with PNG images of intermediate results. + -p Image path color in hex format (#RRGGBB or #RRGGBBAA))END" << std::endl; - exit(exitcode); + std::exit(exitcode); } -static void check_file(const char *filename) { +static void check_file(const char *filename) +{ if (access(filename, R_OK) != 0) { perror(filename); exit(1); } } -Options Options::parse(const int argc, char *const argv[]) { - string model; - string weights; - string means; - string dump; - string labels; +/** + * Parse a color string into a color array. + * + * This function may terminate the program on a partial match. + * + * @param input + * @param targetColor + * @return true if the read was successful. + */ +static bool parse_color(const char *input, std::array &targetColor) +{ + if (input[0] == '#') { + // Attempt to parse #RRGGBBAA + std::array colorBuf; + const int result = std::sscanf(input, "#%02x%02x%02x%02x", &colorBuf[0], &colorBuf[1], &colorBuf[2], + &colorBuf[3]); + CHECK_GE(result, 3) << "Invalid color HEX format, need at least 3 hex pairs, got " << result << "\n"; + + std::transform(colorBuf.begin(), colorBuf.end(), targetColor.begin(), [](auto x) { return x / 255.f; }); + + // Optionally, patch the alpha channel if not specified + if (result == 3) targetColor[3] = 1; + return true; + } + + std::cerr << "Unknown color format used (" << input << ")\n"; + + return false; +} + +Options Options::parse(const int argc, char *const argv[]) +{ + Options options; char c; - while ((c = getopt(argc, argv, "hm:w:n:l:d:")) != -1) { + while ((c = getopt(argc, argv, "hm:w:n:l:d:p:")) != -1) { switch (c) { case 'h': show_help(argv[0], 0); - break; case 'w': check_file(optarg); - weights = optarg; + options.weightsPath = optarg; break; case 'n': check_file(optarg); - model = optarg; + options.modelPath = optarg; break; case 'm': check_file(optarg); - means = optarg; + options.meansPath = optarg; break; case 'l': check_file(optarg); - labels = optarg; + options.labelsPath = optarg; break; case 'd': - dump = optarg; + options.dumpPath = optarg; + break; + + case 'p': + if (!parse_color(optarg, options.pathColor_)) { + show_help(argv[0], 1); + } break; case '?': show_help(argv[0], 1); - break; default: - cerr << "Unhandled option: " << c << endl; + std::cerr << "Unhandled option: " << c << std::endl; abort(); } } - if (weights.empty()) { - cerr << "Weights file is required!" << endl; + if (options.weightsPath.empty()) { + std::cerr << "Weights file is required!\n"; show_help(argv[0], 1); } - if (model.empty()) { - cerr << "Model file is required!" << endl; + if (options.modelPath.empty()) { + std::cerr << "Model file is required!\n"; show_help(argv[0], 1); } - for_each(argv + optind, argv + argc, check_file); - - vector inputs(argv + optind, argv + argc); - if (inputs.empty()) { - cerr << "No inputs specified" << endl; + std::for_each(argv + optind, argv + argc, check_file); + options.inputPaths.insert(options.inputPaths.end(), argv + optind, argv + argc); + if (options.inputPaths.empty()) { + std::cerr << "No inputs specified\n"; show_help(argv[0], 1); } - return Options(move(model), move(weights), move(means), move(labels), move(dump), move(inputs)); + return options; } -Options::Options(string &&model, string &&weights, string&& means, string&& labels, string&& dumpPath, vector &&inputs) noexcept: - modelPath(move(model)), - weightsPath(move(weights)), - meansPath(means), - labelsPath(labels), - dumpPath(dumpPath), - inputPaths(move(inputs)) +const string &Options::model() const { -} - -const string& Options::model() const { return modelPath; } -const string& Options::weights() const { +const string &Options::weights() const +{ return weightsPath; } -const vector& Options::inputs() const { +const vector &Options::inputs() const +{ return inputPaths; } -const string& Options::means() const +const string &Options::means() const { return meansPath; } -optional> Options::labels() const +std::optional> Options::labels() const { - if (labelsPath.empty()) { - return nullopt; + if (!labelsPath) { + return std::nullopt; } else { - return read_vector(labelsPath); + return read_vector(labelsPath); } } std::optional Options::imageDumper() const { - if (dumpPath.empty()) { - return nullopt; + if (!dumpPath) { + return std::nullopt; } else { - return move(PNGDumper(dumpPath)); + return PNGDumper(dumpPath); } } + +Options::Options() noexcept : + pathColor_({1, 1, 1, 0.1}), + labelsPath(nullptr), + dumpPath(nullptr) +{ +} diff --git a/src/fmri/Options.hpp b/src/fmri/Options.hpp index 570ccfa..28cc3c1 100644 --- a/src/fmri/Options.hpp +++ b/src/fmri/Options.hpp @@ -24,13 +24,14 @@ namespace fmri { const vector& inputs() const; private: - const string modelPath; - const string weightsPath; - const string meansPath; - const string labelsPath; - const string dumpPath; - const vector inputPaths; + std::array pathColor_; + string modelPath; + string weightsPath; + string meansPath; + char const * labelsPath; + char const * dumpPath; + vector inputPaths; - Options(string &&, string &&, string&&, string&&, string&&, vector &&) noexcept; + Options() noexcept; }; } diff --git a/src/tools/launcher.cpp b/src/tools/launcher.cpp index 01a11f5..49d5973 100644 --- a/src/tools/launcher.cpp +++ b/src/tools/launcher.cpp @@ -31,6 +31,19 @@ auto file_filter_for_extension(std::string_view extension) return filter; } + +char * color_string(Gtk::ColorButton &button) +{ + char* buffer = new char[2 * 4 + 2]; // 2 per channel, plus #, plus null byte + auto color = button.get_rgba(); + + // Note: Gdk stores its RGBA values in range 0..66535 instead of 0..255, so need to scale. + sprintf(buffer, "#%02x%02x%02x%02x", color.get_red_u() >> 8, + color.get_green_u() >> 8, color.get_blue_u() >> 8, color.get_alpha_u() >> 8); + + return buffer; +} + /** * Wrap string into a dynamically allocated c-string. * @@ -55,6 +68,8 @@ public: ~Launcher() override = default; private: + int rows; + Gtk::Grid grid; Gtk::FileChooserButton fmriChooser; Gtk::FileChooserButton modelChooser; @@ -62,6 +77,7 @@ private: Gtk::FileChooserButton labelChooser; Gtk::FileChooserButton meansChooser; Gtk::FileChooserButton inputChooser; + Gtk::ColorButton pathColor; Gtk::Button startButton; void start(); @@ -69,42 +85,47 @@ private: std::vector getInputFiles(); Gtk::Label* getManagedLabel(const std::string& contents); void findExecutable(); + void addRowWithLabel(const std::string& label, Gtk::Widget& widget); }; Launcher::Launcher() : Gtk::Window(), + rows(0), fmriChooser("Select FMRI executable"), modelChooser("Select caffe model prototxt"), weightsChooser("Select caffe model weights"), labelChooser("Select label text file"), meansChooser("Select means file"), inputChooser("Select input directory", Gtk::FileChooserAction::FILE_CHOOSER_ACTION_SELECT_FOLDER), + pathColor(Gdk::RGBA("rgba(255, 255, 255, 0.1)")), startButton("Start FMRI") { - set_size_request(400, -1); + set_default_size(400, -1); + //set_size_request(400, -1); add(grid); + // Configure all widgets + fmriChooser.set_hexpand(true); + findExecutable(); + modelChooser.add_filter(file_filter_for_extension("prototxt")); + weightsChooser.add_filter(file_filter_for_extension("caffemodel")); + labelChooser.add_filter(file_filter_for_extension("txt")); + meansChooser.add_filter(file_filter_for_extension("binaryproto")); + pathColor.set_use_alpha(true); + + // Configure grid display options grid.set_row_spacing(2); grid.set_column_spacing(2); - findExecutable(); - fmriChooser.set_hexpand(true); - grid.attach(fmriChooser, 1, 0, 1, 1); - grid.attach_next_to(*getManagedLabel("FMRI executable"), fmriChooser, Gtk::PositionType::POS_LEFT, 1, 1); - grid.attach(modelChooser, 1, 1, 1, 1); - modelChooser.add_filter(file_filter_for_extension("prototxt")); - grid.attach_next_to(*getManagedLabel("Model"), modelChooser, Gtk::PositionType::POS_LEFT, 1, 1); - grid.attach(weightsChooser, 1, 2, 1, 1); - weightsChooser.add_filter(file_filter_for_extension("caffemodel")); - grid.attach_next_to(*getManagedLabel("Weights"), weightsChooser, Gtk::PositionType::POS_LEFT, 1, 1); - grid.attach(labelChooser, 1, 3, 1, 1); - labelChooser.add_filter(file_filter_for_extension("txt")); - grid.attach_next_to(*getManagedLabel("Labels (optional)"), labelChooser, Gtk::PositionType::POS_LEFT, 1, 1); - grid.attach(inputChooser, 1, 4, 1, 1); - grid.attach_next_to(*getManagedLabel("Input directory"), inputChooser, Gtk::PositionType::POS_LEFT, 1, 1); - grid.attach(meansChooser, 1, 5, 1, 1); - meansChooser.add_filter(file_filter_for_extension("binaryproto")); - grid.attach_next_to(*getManagedLabel("Means (optional)"), meansChooser, Gtk::PositionType::POS_LEFT, 1, 1); + + // Attach widgets to the grid + addRowWithLabel("FMRI executable", fmriChooser); + addRowWithLabel("Model", modelChooser); + addRowWithLabel("Weights", weightsChooser); + addRowWithLabel("Labels (optional)", labelChooser); + addRowWithLabel("Input directory", inputChooser); + addRowWithLabel("Means (optional)", meansChooser); + addRowWithLabel("Path color", pathColor); startButton.signal_clicked().connect(sigc::mem_fun(*this, &Launcher::start)); grid.attach_next_to(startButton, Gtk::PositionType::POS_BOTTOM, 2, 1); @@ -137,6 +158,8 @@ void Launcher::start() wrap_string(network), wrap_string("-w"), wrap_string(weights), + wrap_string("-p"), + color_string(pathColor), }; if (labelChooser.get_file()) { @@ -222,6 +245,13 @@ void Launcher::findExecutable() } } +void Launcher::addRowWithLabel(const std::string &label, Gtk::Widget &widget) +{ + int currentRow = rows++; + grid.attach(widget, 1, currentRow, 1, 1); + grid.attach_next_to(*getManagedLabel(label), widget, Gtk::PositionType::POS_LEFT, 1, 1); +} + int main(int argc, char** argv) { auto app = Gtk::Application::create(argc, argv);