| 1 | // ============================================================== // |
|---|
| 2 | // // |
|---|
| 3 | // File : NT_branchAnalysis.cxx // |
|---|
| 4 | // Purpose : // |
|---|
| 5 | // // |
|---|
| 6 | // Coded by Ralf Westram (coder@reallysoft.de) in August 2012 // |
|---|
| 7 | // Institute of Microbiology (Technical University Munich) // |
|---|
| 8 | // http://www.arb-home.de/ // |
|---|
| 9 | // // |
|---|
| 10 | // ============================================================== // |
|---|
| 11 | |
|---|
| 12 | #include "NT_local.h" |
|---|
| 13 | #include <TreeCallbacks.hxx> |
|---|
| 14 | #include <aw_awar.hxx> |
|---|
| 15 | #include <awt_canvas.hxx> |
|---|
| 16 | #include <aw_msg.hxx> |
|---|
| 17 | #include <aw_root.hxx> |
|---|
| 18 | #include <awt_config_manager.hxx> |
|---|
| 19 | |
|---|
| 20 | #define AWAR_BRANCH_ANALYSIS "branch_analysis" |
|---|
| 21 | #define AWAR_BRANCH_ANALYSIS_TMP "tmp/" AWAR_BRANCH_ANALYSIS |
|---|
| 22 | |
|---|
| 23 | #define AWAR_BA_MIN_REL_DIFF AWAR_BRANCH_ANALYSIS "/min_rel_diff" |
|---|
| 24 | #define AWAR_BA_MIN_ABS_DIFF AWAR_BRANCH_ANALYSIS "/min_abs_diff" |
|---|
| 25 | #define AWAR_BA_MIN_DEPTH AWAR_BRANCH_ANALYSIS "/min_depth" |
|---|
| 26 | #define AWAR_BA_MIN_ROOTDIST AWAR_BRANCH_ANALYSIS "/min_rootdist" |
|---|
| 27 | #define AWAR_BA_DEGENERATION AWAR_BRANCH_ANALYSIS "/degeneration" |
|---|
| 28 | |
|---|
| 29 | #define AWAR_BA_AUTOMARK_FORMAT AWAR_BRANCH_ANALYSIS_TMP "/auto_%s" |
|---|
| 30 | |
|---|
| 31 | // AISC_MKPT_PROMOTE:class TREE_canvas; |
|---|
| 32 | |
|---|
| 33 | class BranchWindow : virtual Noncopyable { |
|---|
| 34 | TREE_canvas *ntw; |
|---|
| 35 | char *suffix; |
|---|
| 36 | AW_awar *awar_info; |
|---|
| 37 | AW_window_simple *aws; |
|---|
| 38 | |
|---|
| 39 | static char *get_suffix(TREE_canvas *ntw) { |
|---|
| 40 | // suffix depends on canvas |
|---|
| 41 | return GBS_global_string_copy("_%i", ntw->get_index()); |
|---|
| 42 | } |
|---|
| 43 | |
|---|
| 44 | const char *local_awar_name (const char *prefix, const char *name) { return GBS_global_string("%s%s/%s", prefix, suffix, name); } |
|---|
| 45 | |
|---|
| 46 | void create_awars(AW_root *aw_root); |
|---|
| 47 | void create_window(AW_root *aw_root); |
|---|
| 48 | |
|---|
| 49 | const char *automark_awarname() const { return GBS_global_string(AWAR_BA_AUTOMARK_FORMAT, suffix); } |
|---|
| 50 | |
|---|
| 51 | public: |
|---|
| 52 | BranchWindow(AW_root *aw_root, TREE_canvas *ntw_) |
|---|
| 53 | : ntw(ntw_), |
|---|
| 54 | suffix(get_suffix(ntw)) |
|---|
| 55 | { |
|---|
| 56 | create_awars(aw_root); |
|---|
| 57 | create_window(aw_root); |
|---|
| 58 | } |
|---|
| 59 | |
|---|
| 60 | ~BranchWindow() { |
|---|
| 61 | free(suffix); |
|---|
| 62 | } |
|---|
| 63 | |
|---|
| 64 | AW_window *get_window() const { return aws; } |
|---|
| 65 | |
|---|
| 66 | private: |
|---|
| 67 | AW_root *get_awroot() const { return get_window()->get_root(); } |
|---|
| 68 | TREE_canvas *get_canvas() const { return ntw; } |
|---|
| 69 | AP_tree *get_tree() const { return AWT_TREE(ntw)->get_root_node(); } |
|---|
| 70 | GBDATA *get_gbmain() const { return get_canvas()->gb_main; } |
|---|
| 71 | AW_awar *awar(const char *name) { return get_awroot()->awar(name); } |
|---|
| 72 | |
|---|
| 73 | void postmark_action() const { |
|---|
| 74 | AWT_auto_refresh allowed_on(get_canvas()); |
|---|
| 75 | get_canvas()->request_structure_update(); |
|---|
| 76 | } |
|---|
| 77 | |
|---|
| 78 | bool have_tree() { |
|---|
| 79 | if (get_tree()) return true; |
|---|
| 80 | set_info("No tree selected"); |
|---|
| 81 | return false; |
|---|
| 82 | } |
|---|
| 83 | |
|---|
| 84 | static void adapt_intawar_max(AW_awar *awar, int val, int extra) { // @@@ move to AW_awar? |
|---|
| 85 | // adapt maximum defined for int-awar (according to encountered maximum 'val' + a bit 'extra' range) |
|---|
| 86 | nt_assert(awar->get_type() == AW_INT); |
|---|
| 87 | |
|---|
| 88 | int max = awar->get_max(); |
|---|
| 89 | if (max<val || ((val*2) < max && awar->get_min()<val)) { |
|---|
| 90 | awar->set_max(val+extra); |
|---|
| 91 | } |
|---|
| 92 | } |
|---|
| 93 | static void adapt_floatawar_max(AW_awar *awar, double val, double extra) { // @@@ move to AW_awar? |
|---|
| 94 | // adapt maximum defined for float-awar (according to encountered maximum 'val' + a bit 'extra' range) |
|---|
| 95 | nt_assert(awar->get_type() == AW_FLOAT); |
|---|
| 96 | |
|---|
| 97 | double max = awar->get_max(); |
|---|
| 98 | if (max<val || ((val*2) < max && awar->get_min()<val)) { |
|---|
| 99 | awar->set_max(val+extra); |
|---|
| 100 | } |
|---|
| 101 | } |
|---|
| 102 | |
|---|
| 103 | public: |
|---|
| 104 | void set_info(const char *msg) const { awar_info->write_string(msg); } |
|---|
| 105 | void unmark_all() const { NT_mark_all_cb(NULp, get_canvas(), 0); } |
|---|
| 106 | |
|---|
| 107 | AW_awar *automark_awar() const { return get_awroot()->awar(automark_awarname()); } |
|---|
| 108 | bool has_automark_set() const { return automark_awar()->read_int(); } |
|---|
| 109 | |
|---|
| 110 | void markDegeneratedBranches() { |
|---|
| 111 | if (have_tree()) { |
|---|
| 112 | GB_transaction ta(get_gbmain()); |
|---|
| 113 | |
|---|
| 114 | AW_awar *awar_degen = awar(AWAR_BA_DEGENERATION); |
|---|
| 115 | double degeneration_factor = awar_degen->read_float(); |
|---|
| 116 | |
|---|
| 117 | unmark_all(); |
|---|
| 118 | |
|---|
| 119 | double found_max_degeneration = get_tree()->mark_degenerated_branches(degeneration_factor); |
|---|
| 120 | set_info(GBS_global_string("Maximum degeneration = %.2f", found_max_degeneration)); |
|---|
| 121 | adapt_floatawar_max(awar_degen, found_max_degeneration, 1.0); |
|---|
| 122 | |
|---|
| 123 | postmark_action(); |
|---|
| 124 | } |
|---|
| 125 | } |
|---|
| 126 | void markDeepLeafs() { |
|---|
| 127 | if (have_tree()) { |
|---|
| 128 | GB_transaction ta(get_gbmain()); |
|---|
| 129 | |
|---|
| 130 | AW_awar *awar_min_depth = awar(AWAR_BA_MIN_DEPTH); |
|---|
| 131 | AW_awar *awar_min_rootdist = awar(AWAR_BA_MIN_ROOTDIST); |
|---|
| 132 | int min_depth = awar_min_depth->read_int(); |
|---|
| 133 | double min_rootdist = awar_min_rootdist->read_float(); |
|---|
| 134 | int found_max_depth; |
|---|
| 135 | double found_max_rootdist; |
|---|
| 136 | |
|---|
| 137 | unmark_all(); |
|---|
| 138 | set_info(get_tree()->mark_deep_leafs(min_depth, min_rootdist, found_max_depth, found_max_rootdist)); |
|---|
| 139 | adapt_intawar_max(awar_min_depth, found_max_depth, 1); |
|---|
| 140 | adapt_floatawar_max(awar_min_rootdist, found_max_rootdist, 0.1); |
|---|
| 141 | postmark_action(); |
|---|
| 142 | } |
|---|
| 143 | } |
|---|
| 144 | void markLongBranches() { |
|---|
| 145 | if (have_tree()) { |
|---|
| 146 | GB_transaction ta(get_gbmain()); |
|---|
| 147 | |
|---|
| 148 | AW_awar *awar_abs_diff = awar(AWAR_BA_MIN_ABS_DIFF); |
|---|
| 149 | double min_rel_diff = awar(AWAR_BA_MIN_REL_DIFF)->read_float()/100.0; |
|---|
| 150 | double min_abs_diff = awar_abs_diff->read_float(); |
|---|
| 151 | double found_max_abs_diff; |
|---|
| 152 | |
|---|
| 153 | unmark_all(); |
|---|
| 154 | set_info(get_tree()->mark_long_branches(min_rel_diff, min_abs_diff, found_max_abs_diff)); |
|---|
| 155 | adapt_floatawar_max(awar_abs_diff, found_max_abs_diff, 0.1); |
|---|
| 156 | postmark_action(); |
|---|
| 157 | } |
|---|
| 158 | } |
|---|
| 159 | |
|---|
| 160 | void analyseDistances() { |
|---|
| 161 | if (have_tree()) { |
|---|
| 162 | GB_transaction ta(get_gbmain()); |
|---|
| 163 | set_info(get_tree()->analyse_distances()); |
|---|
| 164 | } |
|---|
| 165 | } |
|---|
| 166 | }; |
|---|
| 167 | |
|---|
| 168 | static BranchWindow *existingBranchWindow[MAX_NT_WINDOWS] = { MAX_NT_WINDOWS_NULLINIT }; |
|---|
| 169 | |
|---|
| 170 | // -------------------------------------------------------------------------------- |
|---|
| 171 | |
|---|
| 172 | static void mark_long_branches_cb (AW_window*, BranchWindow *bw) { bw->markLongBranches(); } |
|---|
| 173 | static void mark_deep_leafs_cb (AW_window*, BranchWindow *bw) { bw->markDeepLeafs(); } |
|---|
| 174 | static void mark_degenerated_branches_cb(AW_window*, BranchWindow *bw) { bw->markDegeneratedBranches(); } |
|---|
| 175 | static void unmark_branches_cb (AW_window*, BranchWindow *bw) { bw->unmark_all(); } |
|---|
| 176 | static void distance_analysis_cb (AW_window*, BranchWindow *bw) { bw->analyseDistances(); } |
|---|
| 177 | |
|---|
| 178 | static void tree_changed_cb (AW_root*, BranchWindow *bw) { bw->set_info("<tree has changed>"); } |
|---|
| 179 | |
|---|
| 180 | static BranchWindow *findAutomarkingBranchWindow() { |
|---|
| 181 | for (int w = 0; w<MAX_NT_WINDOWS; ++w) { |
|---|
| 182 | BranchWindow *bw = existingBranchWindow[w]; |
|---|
| 183 | if (bw && bw->has_automark_set()) { |
|---|
| 184 | return bw; |
|---|
| 185 | } |
|---|
| 186 | } |
|---|
| 187 | return NULp; |
|---|
| 188 | } |
|---|
| 189 | |
|---|
| 190 | static void mark_long_branches_automark_cb() { |
|---|
| 191 | BranchWindow *bw = findAutomarkingBranchWindow(); |
|---|
| 192 | if (bw) bw->markLongBranches(); |
|---|
| 193 | } |
|---|
| 194 | static void mark_deep_leafs_automark_cb() { |
|---|
| 195 | BranchWindow *bw = findAutomarkingBranchWindow(); |
|---|
| 196 | if (bw) bw->markDeepLeafs(); |
|---|
| 197 | } |
|---|
| 198 | static void mark_degenerated_branches_automark_cb() { |
|---|
| 199 | BranchWindow *bw = findAutomarkingBranchWindow(); |
|---|
| 200 | if (bw) bw->markDegeneratedBranches(); |
|---|
| 201 | } |
|---|
| 202 | |
|---|
| 203 | static void automark_changed_cb(AW_root *, BranchWindow *bw) { |
|---|
| 204 | static bool avoid_recursion = false; |
|---|
| 205 | if (!avoid_recursion) { |
|---|
| 206 | AW_awar *awar_automark = bw->automark_awar(); |
|---|
| 207 | if (awar_automark->read_int()) { // just activated |
|---|
| 208 | LocallyModify<bool> avoid(avoid_recursion, true); |
|---|
| 209 | |
|---|
| 210 | awar_automark->write_int(0); |
|---|
| 211 | BranchWindow *prev_active = findAutomarkingBranchWindow(); |
|---|
| 212 | if (prev_active) prev_active->automark_awar()->write_int(0); |
|---|
| 213 | awar_automark->write_int(1); |
|---|
| 214 | } |
|---|
| 215 | } |
|---|
| 216 | } |
|---|
| 217 | |
|---|
| 218 | void BranchWindow::create_awars(AW_root *aw_root) { |
|---|
| 219 | awar_info = aw_root->awar_string(local_awar_name(AWAR_BRANCH_ANALYSIS_TMP, "info"), "<No analysis performed yet>"); |
|---|
| 220 | ntw->get_awar_tree()->add_callback(makeRootCallback(tree_changed_cb, this)); |
|---|
| 221 | |
|---|
| 222 | aw_root->awar_float(AWAR_BA_MIN_REL_DIFF, 75)->set_minmax(0, 100)->add_callback(makeRootCallback(mark_long_branches_automark_cb)); |
|---|
| 223 | aw_root->awar_float(AWAR_BA_MIN_ABS_DIFF, 0.01)->set_minmax(0, 20)->add_callback(makeRootCallback(mark_long_branches_automark_cb)); |
|---|
| 224 | |
|---|
| 225 | aw_root->awar_int(AWAR_BA_MIN_DEPTH, 0)->set_minmax(0, 50)->add_callback(makeRootCallback(mark_deep_leafs_automark_cb)); |
|---|
| 226 | aw_root->awar_float(AWAR_BA_MIN_ROOTDIST, 0.9)->set_minmax(0, 20)->add_callback(makeRootCallback(mark_deep_leafs_automark_cb)); |
|---|
| 227 | |
|---|
| 228 | aw_root->awar_float(AWAR_BA_DEGENERATION, 30)->set_minmax(0, 100)->add_callback(makeRootCallback(mark_degenerated_branches_automark_cb)); |
|---|
| 229 | } |
|---|
| 230 | |
|---|
| 231 | static AWT_config_mapping_def branch_analysis_config_mapping[] = { |
|---|
| 232 | { AWAR_BA_MIN_REL_DIFF, "minreldiff" }, |
|---|
| 233 | { AWAR_BA_MIN_ABS_DIFF, "minabsdiff" }, |
|---|
| 234 | { AWAR_BA_MIN_DEPTH, "mindepth" }, |
|---|
| 235 | { AWAR_BA_MIN_ROOTDIST, "minrootdist" }, |
|---|
| 236 | { AWAR_BA_DEGENERATION, "degeneration" }, |
|---|
| 237 | |
|---|
| 238 | { NULp, NULp } |
|---|
| 239 | }; |
|---|
| 240 | |
|---|
| 241 | void BranchWindow::create_window(AW_root *aw_root) { |
|---|
| 242 | aws = new AW_window_simple; |
|---|
| 243 | |
|---|
| 244 | aws->init(aw_root, GBS_global_string("BRANCH_ANALYSIS_%s", suffix), "Branch analysis"); |
|---|
| 245 | aws->load_xfig("ad_branch.fig"); |
|---|
| 246 | |
|---|
| 247 | aws->auto_space(5, 5); |
|---|
| 248 | |
|---|
| 249 | aws->at("close"); |
|---|
| 250 | aws->callback(AW_POPDOWN); |
|---|
| 251 | aws->create_button("CLOSE", "CLOSE", "C"); |
|---|
| 252 | |
|---|
| 253 | aws->at("help"); |
|---|
| 254 | aws->callback(makeHelpCallback("branch_analysis.hlp")); |
|---|
| 255 | aws->create_button("HELP", "HELP", "H"); |
|---|
| 256 | |
|---|
| 257 | AW_awar *awar_automark = aw_root->awar_int(automark_awarname(), 0); |
|---|
| 258 | aws->at("auto"); |
|---|
| 259 | aws->label("Auto mark?"); |
|---|
| 260 | aws->create_toggle(awar_automark->awar_name); |
|---|
| 261 | awar_automark->add_callback(makeRootCallback(automark_changed_cb, this)); |
|---|
| 262 | |
|---|
| 263 | aws->at("sel"); |
|---|
| 264 | aws->create_button(NULp, ntw->get_awar_tree()->awar_name, NULp, "+"); |
|---|
| 265 | |
|---|
| 266 | aws->at("info"); |
|---|
| 267 | aws->create_text_field(awar_info->awar_name); |
|---|
| 268 | |
|---|
| 269 | aws->button_length(28); |
|---|
| 270 | |
|---|
| 271 | aws->at("dist_analyse"); |
|---|
| 272 | aws->callback(makeWindowCallback(distance_analysis_cb, this)); |
|---|
| 273 | aws->create_button("ANALYSE", "Analyse distances in tree"); |
|---|
| 274 | |
|---|
| 275 | aws->at("unmark"); |
|---|
| 276 | aws->callback(makeWindowCallback(unmark_branches_cb, this)); |
|---|
| 277 | aws->create_button("UNMARK", "Unmark all species"); |
|---|
| 278 | |
|---|
| 279 | const int FIELDWIDTH = 10; |
|---|
| 280 | const int SCALERWIDTH = 250; |
|---|
| 281 | |
|---|
| 282 | aws->at("mark_long"); |
|---|
| 283 | aws->callback(makeWindowCallback(mark_long_branches_cb, this)); |
|---|
| 284 | aws->create_button("MARK_LONG", "Mark long branches"); |
|---|
| 285 | |
|---|
| 286 | aws->at("min_rel"); aws->create_input_field_with_scaler(AWAR_BA_MIN_REL_DIFF, FIELDWIDTH, SCALERWIDTH, AW_SCALER_LINEAR); |
|---|
| 287 | aws->at("min_abs"); aws->create_input_field_with_scaler(AWAR_BA_MIN_ABS_DIFF, FIELDWIDTH, SCALERWIDTH, AW_SCALER_EXP_LOWER); |
|---|
| 288 | |
|---|
| 289 | |
|---|
| 290 | aws->at("mark_deep"); |
|---|
| 291 | aws->callback(makeWindowCallback(mark_deep_leafs_cb, this)); |
|---|
| 292 | aws->create_button("MARK_DEEP", "Mark deep leafs"); |
|---|
| 293 | |
|---|
| 294 | aws->at("tree_depth"); aws->create_input_field_with_scaler(AWAR_BA_MIN_DEPTH, FIELDWIDTH, SCALERWIDTH, AW_SCALER_EXP_LOWER); |
|---|
| 295 | aws->at("branch_depth"); aws->create_input_field_with_scaler(AWAR_BA_MIN_ROOTDIST, FIELDWIDTH, SCALERWIDTH, AW_SCALER_EXP_LOWER); |
|---|
| 296 | |
|---|
| 297 | aws->at("mark_degen"); |
|---|
| 298 | aws->callback(makeWindowCallback(mark_degenerated_branches_cb, this)); |
|---|
| 299 | aws->create_button("MARK_DEGENERATED", "Mark degenerated branches"); |
|---|
| 300 | |
|---|
| 301 | aws->at("degen"); aws->create_input_field_with_scaler(AWAR_BA_DEGENERATION, FIELDWIDTH, SCALERWIDTH, AW_SCALER_EXP_LOWER); |
|---|
| 302 | |
|---|
| 303 | aws->at("config"); |
|---|
| 304 | AWT_insert_config_manager(aws, AW_ROOT_DEFAULT, "branch_analysis", branch_analysis_config_mapping); |
|---|
| 305 | } |
|---|
| 306 | |
|---|
| 307 | AW_window *NT_create_branch_analysis_window(AW_root *aw_root, TREE_canvas *ntw) { |
|---|
| 308 | int ntw_id = ntw->get_index(); |
|---|
| 309 | if (!existingBranchWindow[ntw_id]) { |
|---|
| 310 | existingBranchWindow[ntw_id] = new BranchWindow(aw_root, ntw); |
|---|
| 311 | } |
|---|
| 312 | nt_assert(existingBranchWindow[ntw_id]); |
|---|
| 313 | return existingBranchWindow[ntw_id]->get_window(); |
|---|
| 314 | } |
|---|
| 315 | |
|---|