File: em_fitter.cpp

package info (click to toggle)
ausaxs 1.1.8-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 72,592 kB
  • sloc: cpp: 49,853; ansic: 6,901; python: 730; makefile: 18
file content (139 lines) | stat: -rw-r--r-- 7,389 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
// SPDX-License-Identifier: LGPL-3.0-or-later
// Author: Kristian Lytje

#include <CLI/CLI.hpp>

#include <plots/All.h>
#include <em/ImageStack.h>
#include <utility/Utility.h>
#include <utility/Console.h>
#include <utility/Logging.h>
#include <fitter/FitReporter.h>
#include <settings/All.h>
#include <constants/Constants.h>

#include <iostream>

using namespace ausaxs;

int main(int argc, char const *argv[]) {
    std::ios_base::sync_with_stdio(false);
    settings::fit::verbose = true;
    settings::general::supplementary_plots = false;
    bool save_settings = false;

    io::ExistingFile mfile, mapfile, settings;
    CLI::App app{"Fit an EM map to a SAXS measurement."};
    auto input_map = app.add_option("input-map", mapfile, "Path to the EM map.")->check(CLI::ExistingFile);
    auto input_saxs = app.add_option("input-saxs", mfile, "Path to the SAXS measurement.")->check(CLI::ExistingFile);
    app.add_option("--output,-o", settings::general::output, "Output folder to write the results to.")->default_val("output/em_fitter/");
    app.add_flag_callback("--licence",    [] () {console::print_text(constants::licence); exit(0);}, "Print the licence.");
    app.add_flag_callback("-v,--version", [] () {console::print_text(constants::version); exit(0);}, "Print the AUSAXS version.");
    app.add_option("--threads,-t", settings::general::threads, "Number of threads to use.")->default_val(settings::general::threads);

    // config subcommands
    auto sub_config = app.add_subcommand("config", "See and set additional options for the configuration.");
    auto p_settings = sub_config->add_option("--file,-f", settings, "The configuration file to use.")->check(CLI::ExistingFile);
    sub_config->add_flag("--save", save_settings, "Save the settings to a file.");
    sub_config->add_flag_callback("--log", [] () {logging::start("em_fitter");}, "Enable logging to a file.");

    // data subcommands
    auto sub_data = app.add_subcommand("saxs", "See and set additional options for the SAXS data.");
    sub_data->add_option(
        "--qmax", 
        settings::axes::qmax, 
        "Upper limit on used q values from the measurement file.")
        ->default_val(settings::axes::qmax)
        ->check(CLI::Range(constants::axes::q_axis.min, constants::axes::q_axis.max))
    ;
    sub_data->add_option(
        "--qmin", 
        settings::axes::qmin, 
        "Lower limit on used q values from the measurement file.")
        ->default_val(settings::axes::qmin)
        ->check(CLI::Range(constants::axes::q_axis.min, constants::axes::q_axis.max))
    ;
    sub_data->add_option_function<std::string>("--unit,-u", [] (const std::string& s) {settings::detail::parse_option("unit", {s});}, "The unit of the q values in the measurement file. Options: A, nm.");
    sub_data->add_option("--skip", settings::axes::skip, "Number of points to skip in the measurement file.")->default_val(settings::axes::skip);
    sub_data->add_flag("--rebin", settings::flags::data_rebin, "Rebin the data to increase the information content of each data point.")->default_val(settings::flags::data_rebin);

    // em subcommands
    auto sub_em = app.add_subcommand("em", "See and set additional options for the EM map.");
    sub_em->add_option("--levelmin", settings::em::alpha_levels.min, "Lower limit on the alpha levels to use for the EM map. Note that lowering this limit severely impacts the performance and memory load.");
    sub_em->add_option("--levelmax", settings::em::alpha_levels.max, "Upper limit on the alpha levels to use for the EM map. Increasing this limit improves the performance.");
    sub_em->add_option("--charge-levels", settings::em::charge_levels, "Number of charge levels to use for the EM map.");
    sub_em->add_option("--frequency", settings::em::sample_frequency, "Sampling frequency of the EM map.");
    sub_em->add_flag("--hydrate,!--no-hydrate", settings::em::hydrate, "Generate a hydration shell for the protein before fitting.");
    sub_em->add_flag("--fixed-weight,!--dynamic-weight", settings::em::fixed_weights, "Use a fixed weight for the fit.");

    // fit subcommands
    auto sub_fit = app.add_subcommand("fit", "See and set additional options for the fitting process.");
    sub_fit->add_option("--max-iterations", settings::fit::max_iterations, "Maximum number of iterations to perform. This is only approximate.");
    sub_fit->add_flag("--verbose,!--quiet", settings::fit::verbose, "Print the progress of the fit to the console.");

    app.final_callback([&] () {
        // save settings if requested
        if (save_settings) {
            settings::write("settings.txt");
            console::print_info("Settings saved to settings.txt in current directory.");
            if (!input_map->count() || !input_saxs->count()) { // gracefully exit if no input files are provided
                exit(0);
            }
        }

        // required args (not marked ->required() since that interferes with the help flag for subcommands)
        if (!input_map->count() || !input_saxs->count()) {
            console::print_warning("Error: Both input_structure and input_measurement are required.");
            exit(1);
        }
    });

    CLI11_PARSE(app, argc, argv);

    console::print_info("Running AUSAXS " + std::string(constants::version));

    //###################//
    //### PARSE INPUT ###//
    //###################//
    try {
        // if a settings file was provided
        if (p_settings->count() != 0) {
            settings::read(settings);        // read it
            CLI11_PARSE(app, argc, argv);   // re-parse the command line arguments so they take priority
        } else {                            // otherwise check if there is a settings file in the same directory
            if (settings::discover(mfile.directory())) {
                CLI11_PARSE(app, argc, argv);
            }
        }

        // validate input
        if (!constants::filetypes::em_map.check(mapfile)) {
            if (constants::filetypes::em_map.check(mfile)) {
                std::swap(mapfile, mfile);
            } else {
                throw except::invalid_argument("Unknown EM extensions: \"" + mapfile.str() + "\" and \"" + mfile.str() + "\"");
            }
        }
        if (!constants::filetypes::saxs_data.check(mfile)) {
            throw except::invalid_argument("Unknown SAXS data extension: \"" + mfile.str() + "\"");
        }
        if (!settings::em::hydrate) {settings::fit::fit_hydration = false;} 
        if (!settings::general::output.empty() && settings::general::output.back() != '/') {settings::general::output += "/";}
        settings::general::output += mfile.stem() + "/" + mapfile.stem() + "/";

        console::print_text("Performing EM fit with map \"" + mapfile.str() + "\" and measurement \"" + mfile.str() + "\"");
        em::ImageStack map(mapfile); 
        auto res = map.fit(mfile);

        fitter::FitReporter::report(res.get());
        fitter::FitReporter::save(res.get(), {settings::general::output + "report.txt"}, argc, argv);
        res->curves.select_columns({0, 1, 2, 3}).save(
            settings::general::output + "ausaxs.fit",
            "chi2=" + std::to_string(res->fval/res->dof) + " dof=" + std::to_string(res->dof)
        );
    } catch (const std::exception& e) {
        console::print_warning(e.what());
        throw e;
    }
    return 0;
}