From 75964b68f873d27d98c858bb2e6597d3a41b6894 Mon Sep 17 00:00:00 2001 From: kcleal Date: Wed, 10 Jul 2024 16:11:24 +0100 Subject: [PATCH 1/5] v0.10.1. Fixed image clip issues Fixed missing tilde key Fixed missing coverage when linking reads. Fixed LDFLAGS bug. --- .github/workflows/main.yml | 2 +- Makefile | 2 +- src/main.cpp | 2 +- src/menu.cpp | 2 +- src/plot_commands.cpp | 1 + src/plot_controls.cpp | 7 ++ src/plot_manager.cpp | 134 +++++++++++++++++++++++++++++++------ src/themes.cpp | 1 + 8 files changed, 128 insertions(+), 23 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 95586aa..ad76643 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -5,7 +5,7 @@ on: pull_request: env: - version: 0.10.0 + version: 0.10.1 jobs: mingw: diff --git a/Makefile b/Makefile index ec09315..f209b2c 100644 --- a/Makefile +++ b/Makefile @@ -52,7 +52,7 @@ ifneq ($(PLATFORM), "Windows") else # Darwin / Linux CPPFLAGS += -I./lib/skia SKIA_PATH = ./lib/skia/out/Release-x64 - LDFLAGS= + endif endif diff --git a/src/main.cpp b/src/main.cpp index 1798cbc..9155e38 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -84,7 +84,7 @@ int main(int argc, char *argv[]) { static const std::vector links = { "none", "sv", "all" }; // note to developer - update version in workflows/main.yml, menu.cpp and deps/gw.desktop, and installers .md in docs - argparse::ArgumentParser program("gw", "0.10.0"); + argparse::ArgumentParser program("gw", "0.10.1"); program.add_argument("genome") .default_value(std::string{""}).append() diff --git a/src/menu.cpp b/src/menu.cpp index 01532a4..cfd0b0d 100644 --- a/src/menu.cpp +++ b/src/menu.cpp @@ -283,7 +283,7 @@ namespace Menu { std::string tip; if (opts.control_level.empty()) { if (opts.menu_table == Themes::MenuTable::MAIN) { - tip = opts.ini_path + " v0.10.0"; + tip = opts.ini_path + " v0.10.1"; } else if (opts.menu_table == Themes::MenuTable::GENOMES) { tip = "Use ENTER key to select genome, or RIGHT_ARROW key to edit path"; } else if (opts.menu_table == Themes::MenuTable::SHIFT_KEYMAP) { tip = "Change characters selected when using shift+key"; } diff --git a/src/plot_commands.cpp b/src/plot_commands.cpp index ec82dfd..144d871 100644 --- a/src/plot_commands.cpp +++ b/src/plot_commands.cpp @@ -334,6 +334,7 @@ namespace Commands { p->imageCache.clear(); p->imageCacheQueue.clear(); HGW::refreshLinked(p->collections, p->opts, &p->samMaxY); + for (auto &cl: p->collections) { cl.skipDrawingCoverage = false; cl.skipDrawingReads = false;} p->redraw = true; p->processed = true; } diff --git a/src/plot_controls.cpp b/src/plot_controls.cpp index dd0bf70..8cc19bb 100644 --- a/src/plot_controls.cpp +++ b/src/plot_controls.cpp @@ -698,6 +698,7 @@ namespace Manager { last_mode = mode; mode = Show::SETTINGS; opts.menu_table = Themes::MenuTable::MAIN; + for (auto &cl: collections) { cl.skipDrawingCoverage = false; cl.skipDrawingReads = false;} return; } else if (mode == Show::SETTINGS && (action == GLFW_PRESS || action == GLFW_REPEAT)) { if (key == GLFW_KEY_ESCAPE && opts.menu_table == Themes::MenuTable::MAIN) { @@ -705,6 +706,7 @@ namespace Manager { redraw = true; processed = false; updateSettings(); + for (auto &cl: collections) { cl.skipDrawingCoverage = false; cl.skipDrawingReads = false;} } else { bool keep_alive = Menu::navigateMenu(opts, key, action, inputText, &charIndex, &captureText, &textFromSettings, &processText, reference); if (opts.editing_underway) { @@ -1038,6 +1040,7 @@ namespace Manager { currentVarTrack = &variantTracks[variantFileSelection]; currentVarTrack->blockStart = 0; mode = Manager::Show::TILED; + if (print_message) { out << termcolor::magenta << "\nFile " << termcolor::reset << variantTracks[variantFileSelection].path << "\n"; } @@ -1462,6 +1465,7 @@ namespace Manager { yDrag = DRAG_UNSET; redraw = true; processed = false; + for (auto &cl: collections) { cl.skipDrawingCoverage = false; cl.skipDrawingReads = false;} imageCacheQueue.clear(); if (currentVarTrack->type == HGW::TrackType::IMAGES) { currentVarTrack->multiRegions.clear(); @@ -1496,6 +1500,8 @@ namespace Manager { processed = false; fetchRefSeqs(); mode = Manager::SINGLE; + for (auto &cl: collections) { cl.skipDrawingCoverage = false; cl.skipDrawingReads = false;} + imageCacheQueue.clear(); glfwPostEmptyEvent(); } } @@ -1519,6 +1525,7 @@ namespace Manager { processed = false; fetchRefSeqs(); mode = Manager::SINGLE; + for (auto &cl: collections) { cl.skipDrawingCoverage = false; cl.skipDrawingReads = false;} glfwPostEmptyEvent(); } } diff --git a/src/plot_manager.cpp b/src/plot_manager.cpp index fc48512..cf35ab1 100644 --- a/src/plot_manager.cpp +++ b/src/plot_manager.cpp @@ -865,6 +865,7 @@ namespace Manager { continue; } canvasR->save(); + // for now cl.skipDrawingCoverage and cl.skipDrawingReads are almost always the same if ((!cl.skipDrawingCoverage && !cl.skipDrawingReads) || imageCacheQueue.empty()) { if (cl.bamIdx == 0) { // cover the ref too @@ -876,7 +877,7 @@ namespace Manager { } else if (cl.skipDrawingCoverage) { clip.setXYWH(cl.xOffset, cl.yOffset + refSpace, cl.regionPixels, cl.yPixels - covY); canvasR->clipRect(clip, false); - } else { // skip reads + } else if (cl.skipDrawingReads){ // skip reads clip.setXYWH(cl.xOffset, cl.yOffset - covY, cl.regionPixels, covY); canvasR->clipRect(clip, false); } @@ -1365,7 +1366,7 @@ namespace Manager { float newHeight = newWidth / ratio; rect.setXYWH(b.xStart, b.yStart, newWidth, newHeight); } - +// canvas->drawRect(rect, opts.theme.bgPaint); canvas->drawImageRect(imageCache[i], rect, sampOpts); if (currentVarTrack->multiLabels.empty()) { ++i; continue; @@ -1388,8 +1389,55 @@ namespace Manager { processBam(); setScaling(); canvas->drawPaint(opts.theme.bgPaint); + SkRect clip; for (auto &cl: collections) { - Drawing::drawCollection(opts, cl, canvas, trackY, yScaling, fonts, opts.link_op, refSpace, pointSlop, textDrop, pH, monitorScale); + + if (cl.skipDrawingCoverage && cl.skipDrawingReads) { // keep read and coverage area + continue; + } + canvas->save(); + + // for now cl.skipDrawingCoverage and cl.skipDrawingReads are almost always the same + if ((!cl.skipDrawingCoverage && !cl.skipDrawingReads) || imageCacheQueue.empty()) { + if (cl.bamIdx == 0) { // cover the ref too + clip.setXYWH(cl.xOffset, 0, cl.regionPixels, cl.yOffset + trackY + covY + refSpace); + } else { + clip.setXYWH(cl.xOffset, cl.yOffset - covY, cl.regionPixels, cl.yOffset + trackY + covY); + } + canvas->clipRect(clip, false); + } else if (cl.skipDrawingCoverage) { + clip.setXYWH(cl.xOffset, cl.yOffset + refSpace, cl.regionPixels, cl.yPixels - covY); + canvas->clipRect(clip, false); + } else if (cl.skipDrawingReads){ // skip reads + clip.setXYWH(cl.xOffset, cl.yOffset - covY, cl.regionPixels, covY); + canvas->clipRect(clip, false); + } + canvas->drawPaint(opts.theme.bgPaint); + + if (!cl.skipDrawingReads) { + + if (cl.regionLen >= opts.low_memory && !bams.empty() && opts.link_op == 0) { // low memory mode will be used + cl.clear(); + if (opts.threads == 1) { + HGW::iterDraw(cl, bams[cl.bamIdx], headers[cl.bamIdx], indexes[cl.bamIdx], + ®ions[cl.regionIdx], (bool) opts.max_coverage, + filters, opts, canvas, trackY, yScaling, fonts, refSpace, pointSlop, + textDrop, pH, monitorScale); + } else { + HGW::iterDrawParallel(cl, bams[cl.bamIdx], headers[cl.bamIdx], indexes[cl.bamIdx], + opts.threads, ®ions[cl.regionIdx], (bool) opts.max_coverage, + filters, opts, canvas, trackY, yScaling, fonts, refSpace, pool, + pointSlop, textDrop, pH, monitorScale); + } + } else { + Drawing::drawCollection(opts, cl, canvas, trackY, yScaling, fonts, opts.link_op, refSpace, + pointSlop, textDrop, pH, monitorScale); + } + } + canvas->restore(); + + +// Drawing::drawCollection(opts, cl, canvas, trackY, yScaling, fonts, opts.link_op, refSpace, pointSlop, textDrop, pH, monitorScale); } if (opts.max_coverage) { Drawing::drawCoverage(opts, collections, canvas, fonts, covY, refSpace); @@ -1410,7 +1458,6 @@ namespace Manager { if (bams.empty()) { return; } -// SkCanvas* canvas = rasterCanvas; canvas->drawPaint(opts.theme.bgPaint); fetchRefSeqs(); @@ -1444,24 +1491,73 @@ namespace Manager { } } setScaling(); -// canvas->drawPaint(opts.theme.bgPaint); - idx = 0; - for (int i=0; i<(int)bams.size(); ++i) { - htsFile* b = bams[i]; - sam_hdr_t *hdr_ptr = headers[i]; - hts_idx_t *index = indexes[i]; - for (int j=0; j<(int)regions.size(); ++j) { - Utils::Region *reg = ®ions[j]; - if (opts.threads == 1) { - HGW::iterDraw(collections[idx], b, hdr_ptr, index, reg, (bool) opts.max_coverage, - filters, opts, canvas, trackY, yScaling, fonts, refSpace, pointSlop, textDrop, pH, monitorScale); + + SkRect clip; + for (auto &cl: collections) { + if (cl.skipDrawingCoverage && cl.skipDrawingReads) { // keep read and coverage area + continue; + } + canvas->save(); + + // for now cl.skipDrawingCoverage and cl.skipDrawingReads are almost always the same + if ((!cl.skipDrawingCoverage && !cl.skipDrawingReads) || imageCacheQueue.empty()) { + if (cl.bamIdx == 0) { // cover the ref too + clip.setXYWH(cl.xOffset, 0, cl.regionPixels, cl.yOffset + trackY + covY + refSpace); } else { - HGW::iterDrawParallel(collections[idx], b, hdr_ptr, index, opts.threads, reg, (bool) opts.max_coverage, - filters, opts, canvas, trackY, yScaling, fonts, refSpace, pool, pointSlop, textDrop, pH, monitorScale); + clip.setXYWH(cl.xOffset, cl.yOffset - covY, cl.regionPixels, cl.yOffset + trackY + covY); } - idx += 1; + canvas->clipRect(clip, false); + } else if (cl.skipDrawingCoverage) { + clip.setXYWH(cl.xOffset, cl.yOffset + refSpace, cl.regionPixels, cl.yPixels - covY); + canvas->clipRect(clip, false); + } else if (cl.skipDrawingReads){ // skip reads + clip.setXYWH(cl.xOffset, cl.yOffset - covY, cl.regionPixels, covY); + canvas->clipRect(clip, false); } - } + canvas->drawPaint(opts.theme.bgPaint); + + if (!cl.skipDrawingReads) { + + if (cl.regionLen >= opts.low_memory && !bams.empty() && opts.link_op == 0) { // low memory mode will be used + cl.clear(); + if (opts.threads == 1) { + HGW::iterDraw(cl, bams[cl.bamIdx], headers[cl.bamIdx], indexes[cl.bamIdx], + ®ions[cl.regionIdx], (bool) opts.max_coverage, + filters, opts, canvas, trackY, yScaling, fonts, refSpace, pointSlop, + textDrop, pH, monitorScale); + } else { + HGW::iterDrawParallel(cl, bams[cl.bamIdx], headers[cl.bamIdx], indexes[cl.bamIdx], + opts.threads, ®ions[cl.regionIdx], (bool) opts.max_coverage, + filters, opts, canvas, trackY, yScaling, fonts, refSpace, pool, + pointSlop, textDrop, pH, monitorScale); + } + } else { + Drawing::drawCollection(opts, cl, canvas, trackY, yScaling, fonts, opts.link_op, refSpace, + pointSlop, textDrop, pH, monitorScale); + } + } + canvas->restore(); + } + + + +// idx = 0; +// for (int i=0; i<(int)bams.size(); ++i) { +// htsFile* b = bams[i]; +// sam_hdr_t *hdr_ptr = headers[i]; +// hts_idx_t *index = indexes[i]; +// for (int j=0; j<(int)regions.size(); ++j) { +// Utils::Region *reg = ®ions[j]; +// if (opts.threads == 1) { +// HGW::iterDraw(collections[idx], b, hdr_ptr, index, reg, (bool) opts.max_coverage, +// filters, opts, canvas, trackY, yScaling, fonts, refSpace, pointSlop, textDrop, pH, monitorScale); +// } else { +// HGW::iterDrawParallel(collections[idx], b, hdr_ptr, index, opts.threads, reg, (bool) opts.max_coverage, +// filters, opts, canvas, trackY, yScaling, fonts, refSpace, pool, pointSlop, textDrop, pH, monitorScale); +// } +// idx += 1; +// } +// } if (opts.max_coverage) { Drawing::drawCoverage(opts, collections, canvas, fonts, covY, refSpace); } diff --git a/src/themes.cpp b/src/themes.cpp index 8880643..3327035 100644 --- a/src/themes.cpp +++ b/src/themes.cpp @@ -537,6 +537,7 @@ namespace Themes { shift_keymap["8"] = "*"; shift_keymap["9"] = "("; shift_keymap["0"] = ")"; + shift_keymap["`"] = "~"; } } From 6ec251e57ccf41d41fd955ac1baf5365267a7af3 Mon Sep 17 00:00:00 2001 From: kcleal Date: Thu, 11 Jul 2024 17:32:33 +0100 Subject: [PATCH 2/5] Faster scrolling. Improved save and load functions. Ability to add comments to labels. Added version check on start up. Several bug fixes. --- .gw.ini | 1 + deps/gw.desktop | 2 +- src/drawing.cpp | 5 -- src/hts_funcs.cpp | 6 +- src/main.cpp | 14 +++- src/menu.cpp | 7 +- src/menu.h | 4 +- src/parser.cpp | 22 +++-- src/plot_commands.cpp | 188 +++++++++++++++++++++++++++++++++++------- src/plot_controls.cpp | 117 ++++++++++++++++++++------ src/plot_manager.cpp | 80 +++++++++++------- src/plot_manager.h | 4 +- src/term_out.cpp | 93 ++++++++++++++++++--- src/term_out.h | 2 + src/themes.cpp | 6 +- src/themes.h | 2 +- src/utils.cpp | 13 ++- src/utils.h | 4 +- test/vaf.vcf | 23 ++++++ 19 files changed, 469 insertions(+), 124 deletions(-) create mode 100644 test/vaf.vcf diff --git a/.gw.ini b/.gw.ini index a2e1ae8..6876cb2 100644 --- a/.gw.ini +++ b/.gw.ini @@ -64,6 +64,7 @@ print_screen=PRINT_SCREEN find_alignments=F repeat_command=R vcf_as_tracks=false +bed_as_tracks=true [labelling] number=3x3 diff --git a/deps/gw.desktop b/deps/gw.desktop index 2a072a3..77f804c 100644 --- a/deps/gw.desktop +++ b/deps/gw.desktop @@ -1,6 +1,6 @@ [Desktop Entry] Encoding=UTF-8 -Version=0.10.0 +Version=0.10.1 Type=Application Terminal=true Exec=bash -c "/usr/bin/gw" diff --git a/src/drawing.cpp b/src/drawing.cpp index 6502128..361ba85 100644 --- a/src/drawing.cpp +++ b/src/drawing.cpp @@ -61,9 +61,6 @@ namespace Drawing { float yOffsetAll = refSpace; for (auto &cl: collections) { - if (cl.skipDrawingReads && cl.skipDrawingCoverage) { - continue; - } cl.skipDrawingCoverage = true; if (cl.region->markerPos != -1) { float rp = refSpace + 6 + (cl.bamIdx * cl.yPixels); @@ -1195,8 +1192,6 @@ namespace Drawing { float textW = fonts.overlayWidth; float minLetterSize = (textW > 0) ? ((float) fb_width / (float) regions.size()) / textW : 0; int index = 0; - //h *= 0.7; -// h = (h - 6 < 4) ? 4 : h - 6; float yp = h + 2; for (auto &rgn: regions) { int size = rgn.end - rgn.start; diff --git a/src/hts_funcs.cpp b/src/hts_funcs.cpp index 02af9cf..ad2247b 100644 --- a/src/hts_funcs.cpp +++ b/src/hts_funcs.cpp @@ -2043,6 +2043,7 @@ namespace HGW { // variant id is either recorded in the filename, or else is the whole filename void GwVariantTrack::appendImageLabels(int startIdx, int number) { // rid is the file name for an image + std::string empty_comment; for (int i=startIdx; i < startIdx + number; ++i) { if (i < (int)multiLabels.size()) { continue; @@ -2057,7 +2058,7 @@ namespace HGW { if (inputLabels->contains(key)) { multiLabels.push_back((*inputLabels)[key]); } else { - multiLabels.push_back(Utils::makeLabel(info.chrom, info.pos, label, labelChoices, key, info.varType, "", false, false)); + multiLabels.push_back(Utils::makeLabel(info.chrom, info.pos, label, labelChoices, key, info.varType, "", false, false, empty_comment)); } } } @@ -2067,6 +2068,7 @@ namespace HGW { std::vector v; bool isTrans = chrom != chrom2; Utils::Region* r1; Utils::Region* r2; + std::string empty_comment; if (!isTrans && rlen <= m_opts->split_view_size) { v.resize(1); r1 = & v[0]; @@ -2094,7 +2096,7 @@ namespace HGW { if (inputLabels->contains(rid)) { multiLabels.push_back((*inputLabels)[rid]); } else { - multiLabels.push_back(Utils::makeLabel(chrom, start, label, labelChoices, rid, vartype, "", false, false)); + multiLabels.push_back(Utils::makeLabel(chrom, start, label, labelChoices, rid, vartype, "", false, false, empty_comment)); } } diff --git a/src/main.cpp b/src/main.cpp index 9155e38..d2ae573 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -66,6 +66,9 @@ void print_banner() { #endif } +// note to developer - update version in workflows/main.yml, menu.cpp, term_out.cpp, and deps/gw.desktop, and installers .md in docs +const char GW_VERSION [7] = "0.10.1"; + int main(int argc, char *argv[]) { @@ -83,8 +86,7 @@ int main(int argc, char *argv[]) { static const std::vector img_themes = { "igv", "dark", "slate" }; static const std::vector links = { "none", "sv", "all" }; - // note to developer - update version in workflows/main.yml, menu.cpp and deps/gw.desktop, and installers .md in docs - argparse::ArgumentParser program("gw", "0.10.1"); + argparse::ArgumentParser program("gw", std::string(GW_VERSION)); program.add_argument("genome") .default_value(std::string{""}).append() @@ -376,6 +378,8 @@ int main(int argc, char *argv[]) { if (program.is_used("-b")) { auto bam_paths_temp = program.get>("-b"); for (auto &item : bam_paths_temp) { + bam_paths.push_back(item); + continue; if (std::filesystem::exists(item)) { bam_paths.push_back(item); std::cerr << item << std::endl; @@ -529,6 +533,8 @@ int main(int argc, char *argv[]) { std::exit(-1); } iopts.getOptionsFromSessionIni(iopts.seshIni); + } else { + iopts.session_file = ""; } /* * / Gw start @@ -659,7 +665,6 @@ int main(int argc, char *argv[]) { if (program.is_used("--out-labels")) { plotter.setOutLabelFile(program.get("--out-labels")); } - plotter.addVariantTrack(img, iopts.start_index, false, true); if (plotter.variantTracks.back().image_glob.size() == 1) { plotter.opts.number.x = 1; plotter.opts.number.y = 1; @@ -933,6 +938,7 @@ int main(int argc, char *argv[]) { std::string dateStr; std::string fileName; + std::string empty_comment; std::filesystem::path fsp(vcf.path); #if defined(_WIN32) || defined(_WIN64) const wchar_t* pc = fsp.filename().c_str(); @@ -956,7 +962,7 @@ int main(int argc, char *argv[]) { job.rid = vcf.rid; jobs.push_back(job); if (writeLabel) { - Utils::Label l = Utils::makeLabel(vcf.chrom, vcf.start, vcf.label, empty_labels, vcf.rid, vcf.vartype, "", false, false); + Utils::Label l = Utils::makeLabel(vcf.chrom, vcf.start, vcf.label, empty_labels, vcf.rid, vcf.vartype, "", false, false, empty_comment); Utils::labelToFile(fLabels, l, dateStr, fileName); } } diff --git a/src/menu.cpp b/src/menu.cpp index cfd0b0d..aab4956 100644 --- a/src/menu.cpp +++ b/src/menu.cpp @@ -281,6 +281,7 @@ namespace Menu { } // write tool tip std::string tip; + if (opts.control_level.empty()) { if (opts.menu_table == Themes::MenuTable::MAIN) { tip = opts.ini_path + " v0.10.1"; @@ -294,7 +295,8 @@ namespace Menu { else if (opts.menu_level == "coverage") { tip = "Turn coverage on or off [true, false]"; } else if (opts.menu_level == "log2_cov") { tip = "Change the y-scale of the coverage track to log2 [true, false]"; } else if (opts.menu_level == "expand_tracks") { tip = "Expand overlapping track features [true, false]"; } - else if (opts.menu_level == "vcf_as_tracks") { tip = "Drag-and-dropped vcf/bcf files will be added as track features [true, false]"; } + else if (opts.menu_level == "vcf_as_tracks") { tip = "Drag-and-dropped vcf/bcf files will be added as a track if true, or image-tiles otherwise [true, false]"; } + else if (opts.menu_level == "bed_as_tracks") { tip = "Drag-and-dropped bed files will be added as a track if true, or image-tiles otherwise [true, false]"; } else if (opts.menu_level == "link") { tip = "Change which reads are linked [none, sv, all]"; } else if (opts.menu_level == "split_view_size") { tip = "Structural variants greater than this size will be drawn as two regions"; } else if (opts.menu_level == "threads") { tip = "The number of threads to use for file readings"; } @@ -682,7 +684,7 @@ namespace Menu { for (const auto& v : {"scroll_speed", "tabix_track_height"}) { option_map[v] = Float; } - for (const auto& v : {"coverage", "log2_cov", "expand_tracks", "vcf_as_tracks", "sv_arcs", "mods"}) { + for (const auto& v : {"coverage", "log2_cov", "expand_tracks", "vcf_as_tracks", "bed_as_tracks", "sv_arcs", "mods"}) { option_map[v] = Bool; } for (const auto& v : {"scroll_right", "scroll_left", "zoom_out", "zoom_in", "scroll_down", "scroll_up", "cycle_link_mode", "print_screen", "find_alignments", "delete_labels", "enter_interactive_mode"}) { @@ -755,6 +757,7 @@ namespace Menu { else if (new_opt.name == "log2_cov") { opts.log2_cov = v; } else if (new_opt.name == "expand_tracks") { opts.expand_tracks = v; } else if (new_opt.name == "vcf_as_tracks") { opts.vcf_as_tracks = v; } + else if (new_opt.name == "bed_as_tracks") { opts.bed_as_tracks = v; } else if (new_opt.name == "coverage") { opts.max_coverage = (v) ? 1410065408 : 0; } else if (new_opt.name == "sv_arcs") { opts.sv_arcs = v; } else if (new_opt.name == "mods") { opts.parse_mods = v; } diff --git a/src/menu.h b/src/menu.h index bf661ce..ac56929 100644 --- a/src/menu.h +++ b/src/menu.h @@ -49,8 +49,8 @@ namespace Menu { std::vector getCommandTip(); - constexpr std::array commandToolTip = {"ylim", "var", "tlen-y", "tags", "soft-clips", "snapshot", "sam", "remove", - "refresh", "online", "mods", "mismatches", "mate", "mate add", "log2-cov", "link", "line", "insertions", "indel-length", + constexpr std::array commandToolTip = {"ylim", "var", "tlen-y", "tags", "soft-clips", "save", "sam", "remove", + "refresh", "online", "mods", "mismatches", "mate", "mate add", "log2-cov", "load", "link", "line", "insertions", "indel-length", "grid", "find", "filter", "expand-tracks", "edges", "cov", "count", "add"}; constexpr std::array exec = {"cov", "count", "edges", "expand-tracks", "insertions", "line", "log2-cov", "mate", "mate add", "mismatches", "mods", "tags", "soft-clips", "sam", "refresh", "tlen-y"}; diff --git a/src/parser.cpp b/src/parser.cpp index 5d57d85..97c1a24 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -17,8 +17,8 @@ namespace Parse { - constexpr std::string_view numeric_like = "eq ne gt lt ge le == != > < >= <="; - constexpr std::string_view string_like = "eq ne contains == != omit"; + constexpr std::string_view numeric_like = "eq ne gt lt ge le = == != > < >= <="; + constexpr std::string_view string_like = "eq ne contains = == != omit"; Parser::Parser(std::ostream& errOutput) : out(errOutput) { opMap["mapq"] = MAPQ; @@ -66,6 +66,7 @@ namespace Parse { opMap["ge"] = GE; opMap["le"] = LE; opMap["=="] = EQ; + opMap["="] = EQ; opMap["!="] = NE; opMap[">"] = GT; opMap["<"] = LT; @@ -976,6 +977,14 @@ namespace Parse { void tryTabCompletion(std::string &inputText, std::ostream& out, int& charIndex) { std::vector parts = Utils::split(inputText, ' '); + if (parts.size() == 3) { // chunk first two options + std::string tmp = parts[0]; + std::string tmp2 = parts[1]; + std::string tmp3 = parts[2]; + parts[0] = tmp + " " + tmp2; + parts[1] = tmp3; + parts.resize(2); + } std::string globstr; if (parts.back() == "./") { globstr = "./*"; @@ -986,6 +995,9 @@ namespace Parse { std::vector glob_paths = glob_cpp::glob(globstr); if (glob_paths.size() == 1) { inputText = parts[0] + " " + glob_paths[0].generic_string(); + if (std::filesystem::is_directory(glob_paths[0])) { + inputText += std::filesystem::path::preferred_separator; + } charIndex = inputText.size(); return; } @@ -998,6 +1010,7 @@ namespace Parse { return; } inputText = parts[0] + " " + lcp; + charIndex = inputText.size(); size_t width = (size_t)Utils::get_terminal_width() - 1; size_t i = 0; @@ -1009,18 +1022,17 @@ namespace Parse { } else { if (width > 6 && s.size() > 6) { s.erase(s.begin() + width - 4, s.end()); - out << s << " ..."; + out << s << " ..."; out.flush(); } break; } if (i < glob_paths.size() - 2 && width > 2) { - out << " "; + out << " "; out.flush(); width -= 2; } else { break; } } - out.flush(); return; } } diff --git a/src/plot_commands.cpp b/src/plot_commands.cpp index 144d871..ea197f5 100644 --- a/src/plot_commands.cpp +++ b/src/plot_commands.cpp @@ -334,7 +334,7 @@ namespace Commands { p->imageCache.clear(); p->imageCacheQueue.clear(); HGW::refreshLinked(p->collections, p->opts, &p->samMaxY); - for (auto &cl: p->collections) { cl.skipDrawingCoverage = false; cl.skipDrawingReads = false;} + for (auto &cl: p->collections) { cl.skipDrawingCoverage = true; cl.skipDrawingReads = false;} p->redraw = true; p->processed = true; } @@ -542,11 +542,12 @@ namespace Commands { out << termcolor::red << "Error:" << termcolor::reset << " ylim invalid value\n"; return Err::NONE; } - p->imageCache.clear(); - p->imageCacheQueue.clear(); - HGW::refreshLinked(p->collections, p->opts, &p->samMaxY); - p->processed = true; - p->redraw = true; + refreshGw(p); +// p->imageCache.clear(); +// p->imageCacheQueue.clear(); +// HGW::refreshLinked(p->collections, p->opts, &p->samMaxY); +// p->processed = true; +// p->redraw = true; return Err::NONE; } @@ -578,7 +579,7 @@ namespace Commands { ind = std::stoi(parts.back()); } catch (...) { out << termcolor::red << "Error:" << termcolor::reset << " bam index not understood\n"; - return Err::NONE; + return Err::SILENT; } p->removeBam(ind); } else if (Utils::startsWith(parts.back(), "track")) { @@ -587,7 +588,7 @@ namespace Commands { ind = std::stoi(parts.back()); } catch (...) { out << termcolor::red << "Error:" << termcolor::reset << " track index not understood\n"; - return Err::NONE; + return Err::SILENT; } p->removeTrack(ind); } else { @@ -595,10 +596,12 @@ namespace Commands { ind = std::stoi(parts.back()); } catch (...) { out << termcolor::red << "Error:" << termcolor::reset << " region index not understood\n"; - return Err::NONE; + return Err::SILENT; } p->removeRegion(ind); + } + p->redraw = true; bool clear_filters = false; // removing a region can invalidate indexes so remove them for (auto &f : p->filters) { if (!f.targetIndexes.empty()) { @@ -609,7 +612,11 @@ namespace Commands { if (clear_filters) { p->filters.clear(); } - p->imageCacheQueue.clear(); + for (auto &cl : p->collections) { + cl.skipDrawingReads = false; + cl.skipDrawingCoverage = false; + } + p->imageCacheQueue.clear(); return Err::NONE; } @@ -1049,15 +1056,127 @@ namespace Commands { out << "Saved session: " << parts.back() << std::endl; p->saveSession(parts.back()); } + else if (Utils::endsWith(parts.back(), ".tsv") || (Utils::endsWith(parts.back(), ".txt"))) { + out << "Output label file set as: " << parts.back() << std::endl; + p->setOutLabelFile(parts.back()); + p->saveLabels(); + } else { + if (parts.back() == "labels") { + out << "Current label path is: " << p->outLabelFile << std::endl; + p->saveLabels(); + return Err::NONE; + } + return Err::INVALID_PATH; + } return Err::NONE; } - Err load_file(Plot* p, std::vector parts) { - if (parts.size() != 2) { + Err load_file(Plot* p, std::vector parts, std::ostream& out) { + std::string filename; + + if (parts.size() == 3) { + filename = Parse::tilde_to_home(parts.back()); + std::string ext = std::filesystem::path(filename).extension(); + if (std::filesystem::is_directory(filename)) { + out << termcolor::red << "Error:" << termcolor::reset << " This is a folder path, not a file\n"; + return Err::SILENT; + } + if (parts[1] == "ideogram") { + if (!std::filesystem::exists(parts.back())) { + return Err::INVALID_PATH; + } + if (ext != ".bed") { + out << termcolor::red << "Error:" << termcolor::reset << " Only .bed extension supported for ideograms\n"; + return Err::SILENT; + } else { + p->addIdeogram(filename); + refreshGw(p); + return Err::NONE; + } + } else if (parts[1] == "track") { + if (!std::filesystem::exists(parts.back())) { + return Err::INVALID_PATH; + } + if (ext == ".bam" || ext == ".cram") { + out << termcolor::red << "Error:" << termcolor::reset << " Bam/cram can not be loaded as a track\n"; + return Err::SILENT; + } else { + p->addTrack(filename, true, true, true); + refreshGw(p); + return Err::NONE; + } + + } else if (parts[1] == "tiled") { + if (!std::filesystem::exists(parts.back())) { + return Err::INVALID_PATH; + } + if (ext == ".vcf" || ext == ".vcf.gz" || ext == ".bcf" || ext == ".bed" || ext == ".bed.gz") { + p->addTrack(filename, true, false, false); + refreshGw(p); + return Err::NONE; + } else { + out << termcolor::red << "Error:" << termcolor::reset << " Image tiling only supported for .vcf|.vcf.gz|.bcf|.bed|.bed.gz file extensions\n"; + return Err::SILENT; + } + } else if (parts[1] == "bam" || parts[1] == "cram") { + if (!std::filesystem::exists(parts.back())) { + return Err::INVALID_PATH; + } + p->addTrack(filename, true, p->opts.vcf_as_tracks, p->opts.bed_as_tracks); + refreshGw(p); + return Err::NONE; + } else if (parts[1] == "genome") { + p->loadGenome(filename, out); + refreshGw(p); + return Err::NONE; + } else if (parts[1] == "labels") { + if (!std::filesystem::exists(parts.back())) { + return Err::INVALID_PATH; + } + p->seenLabels.clear(); + std::string img; + std::vector labels = Utils::split_keep_empty_str(p->opts.labels, ','); + Utils::openLabels(parts.back(), img, p->inputLabels, labels, p->seenLabels); + std::vector current; + std::vector idx; + for (auto &v : p->variantTracks) { + current.push_back(v.path); + idx.push_back(v.blockStart); + } + p->variantTracks.clear(); + + int i = 0; + for (auto &v : current) { + p->addVariantTrack(v, idx[i], false, false); // dont cache, dont use full path as filename + i += 1; + } + refreshGw(p); + return Err::NONE; + } else { + return Err::OPTION_NOT_UNDERSTOOD; + } + } else if (parts.size() != 2) { return Err::OPTION_NOT_UNDERSTOOD; } - std::string filename = Parse::tilde_to_home(parts.back()); - p->addTrack(filename, true); + if (!std::filesystem::exists(parts.back())) { + if (parts.back() == "labels") { + out << "Current label path is: " << p->outLabelFile << std::endl; + refreshGw(p); + return Err::NONE; + } else if (parts.back() == "genome") { + out << "Current genome path is: " << p->reference << std::endl; + refreshGw(p); + return Err::NONE; + } else if (parts.back() == "ideogram") { + out << "Current ideogram path is: " << p->ideogram_path << std::endl; + refreshGw(p); + return Err::NONE; + } + return Err::INVALID_PATH; + } + filename = Parse::tilde_to_home(parts.back()); + p->addTrack(filename, true, p->opts.vcf_as_tracks, p->opts.bed_as_tracks); + refreshGw(p); return Err::NONE; } @@ -1236,16 +1355,17 @@ namespace Commands { return Err::NONE; } - void save_command_or_handle_err(Err result, std::ostream& out, + void cache_command_or_handle_err(Plot* p, Err result, std::ostream& out, std::vector* applied, std::string& command) { switch (result) { case NONE: applied->push_back(command); - break; + return; case UNKNOWN: out << termcolor::red << "Error:" << termcolor::reset << " Unknown error\n"; break; - case SILENT: break; + case SILENT: + break; case TOO_MANY_OPTIONS: out << termcolor::red << "Error:" << termcolor::reset << " Too many options supplied\n"; break; @@ -1286,6 +1406,13 @@ namespace Commands { out << termcolor::red << "Error:" << termcolor::reset << " Input could not be parsed\n"; break; } + if (p->mode == Manager::Show::SINGLE) { + p->redraw = false; + for (auto &cl : p->collections) { + cl.skipDrawingReads = true; + cl.skipDrawingCoverage = true; + } + } } // Command functions capture these parameters only @@ -1294,7 +1421,14 @@ namespace Commands { // Note the function map will be cached after first call. plt is bound, but parts are updated with each call void run_command_map(Plot* p, std::string& command, std::ostream& out) { if (Utils::startsWith(command, "'") || Utils::startsWith(command, "\"")) { + command.erase(command.begin(), command.begin() + 1); out << command << std::endl; + if (p->mode == Manager::Show::TILED && !p->variantTracks.empty() && p->currentVarTrack != nullptr) { + int i = p->mouseOverTileIndex + p->currentVarTrack->blockStart; + if (i < p->currentVarTrack->multiLabels.size()) { + p->currentVarTrack->multiLabels[i].comment = command; + } + } command = ""; return; } @@ -1314,7 +1448,7 @@ namespace Commands { {"insertions", PARAMS { return insertions(p); }}, {"mm", PARAMS { return mismatches(p); }}, {"mismatches", PARAMS { return mismatches(p); }}, - {"mods", PARAMS { return mods(p); }}, + {"mods", PARAMS { return mods(p); }}, {"edges", PARAMS { return edges(p); }}, {"soft-clips", PARAMS { return soft_clips(p); }}, {"log2-cov", PARAMS { return log2_cov(p); }}, @@ -1329,9 +1463,9 @@ namespace Commands { {"v", PARAMS { return var_info(p, command, parts, out); }}, {"var", PARAMS { return var_info(p, command, parts, out); }}, {"save", PARAMS { return save_command(p, command, parts, out); }}, - {"colour", PARAMS { return update_colour(p, command, parts, out); }}, - {"color", PARAMS { return update_colour(p, command, parts, out); }}, - {"roi", PARAMS { return add_roi(p, command, parts, out); }}, + {"colour", PARAMS { return update_colour(p, command, parts, out); }}, + {"color", PARAMS { return update_colour(p, command, parts, out); }}, + {"roi", PARAMS { return add_roi(p, command, parts, out); }}, {"count", PARAMS { return count(p, command, out); }}, {"filter", PARAMS { return addFilter(p, command, out); }}, @@ -1351,9 +1485,8 @@ namespace Commands { {"s", PARAMS { return snapshot(p, parts, out); }}, {"snapshot", PARAMS { return snapshot(p, parts, out); }}, {"online", PARAMS { return online(p, parts, out); }}, - + {"load", PARAMS { return load_file(p, parts, out); }}, {"grid", PARAMS { return grid(p, parts); }}, - {"load", PARAMS { return load_file(p, parts); }}, }; @@ -1364,13 +1497,8 @@ namespace Commands { } else { res = infer_region_or_feature(p, command, parts); } - save_command_or_handle_err(res, out, &p->commandsApplied, command); + cache_command_or_handle_err(p, res, out, &p->commandsApplied, command); p->inputText = ""; - if (p->redraw) { - for (auto &cl : p->collections) { // todo use this inside commands, not here - cl.skipDrawingReads = false; - cl.skipDrawingCoverage = false; - } - } + } } \ No newline at end of file diff --git a/src/plot_controls.cpp b/src/plot_controls.cpp index 8cc19bb..5e10ed1 100644 --- a/src/plot_controls.cpp +++ b/src/plot_controls.cpp @@ -265,6 +265,12 @@ namespace Manager { return key; } else if (key == GLFW_KEY_TAB) { if (mode != SETTINGS) { + if (Utils::startsWith(inputText, "load ") && !(inputText == "load ")) { + Term::clearLine(out); + Parse::tryTabCompletion(inputText, out, charIndex); + commandToolTipIndex = -1; + return GLFW_KEY_UNKNOWN; + } TipBounds tip_bounds = getToolTipBounds(inputText); if (tip_bounds.lower == tip_bounds.upper) { inputText = tip_bounds.cmd; @@ -286,6 +292,7 @@ namespace Manager { if (key == GLFW_KEY_ENTER || key == GLFW_KEY_KP_ENTER ) { inputText = Menu::commandToolTip[commandToolTipIndex]; charIndex = (int)inputText.size(); + // immediately execute functions that don't need additional args if (std::find( Menu::exec.begin(), Menu::exec.end(), inputText) != Menu::exec.end() || (inputText == "online" && !opts.genome_tag.empty())) { captureText = false; processText = true; @@ -383,7 +390,7 @@ namespace Manager { if (Utils::startsWith(inputText, "load ") && !(inputText == "load ")) { Term::clearLine(out); Parse::tryTabCompletion(inputText, out, charIndex); -// commandToolTipIndex = -1; + commandToolTipIndex = -1; return GLFW_KEY_UNKNOWN; } if (commandToolTipIndex <= 0 || commandToolTipIndex <= tip_bounds.lower) { @@ -610,6 +617,64 @@ namespace Manager { return term_width; } + void GwPlot::loadGenome(std::string genome_tag_or_path, std::ostream& outerr) { + + if (opts.myIni.get("genomes").has(genome_tag_or_path) && reference != opts.myIni["genomes"][genome_tag_or_path]) { + std::cout << opts.myIni["genomes"][opts.genome_tag] << std::endl; + faidx_t *fai_test = fai_load(opts.myIni["genomes"][genome_tag_or_path].c_str()); + if (fai_test != nullptr) { + reference = opts.myIni["genomes"][genome_tag_or_path]; + opts.genome_tag = genome_tag_or_path; + fai = fai_load(reference.c_str()); + for (auto &bm: bams) { + hts_set_fai_filename(bm, reference.c_str()); + } + outerr << termcolor::bold << "\n" << opts.genome_tag << termcolor::reset << " loaded from " << reference << std::endl; + } else { + outerr << termcolor::red << "Error:" << termcolor::reset << " could not open tag " << opts.myIni["genomes"][opts.genome_tag].c_str() << std::endl; + } + fai_destroy(fai_test); + } else { + faidx_t *fai_test = fai_load( genome_tag_or_path.c_str()); + if (fai_test != nullptr) { + reference = genome_tag_or_path; + fai = fai_load(reference.c_str()); + for (auto &bm: bams) { + hts_set_fai_filename(bm, reference.c_str()); + } + outerr << termcolor::bold << "\nGenome" << termcolor::reset << " loaded from " << reference << std::endl; + } else { + outerr << termcolor::red << "Error:" << termcolor::reset << " could not open " << genome_tag_or_path << std::endl; + } + } + if (opts.myIni.get("tracks").has(opts.genome_tag)) { + std::vector track_paths_temp = Utils::split(opts.myIni["tracks"][opts.genome_tag], ','); + for (auto &trk_item : track_paths_temp) { + if (!Utils::is_file_exist(trk_item)) { + outerr << "Warning: track file does not exists - " << trk_item << std::endl; + } else { + bool already_loaded = false; + for (const auto ¤t_track : tracks) { + if (current_track.path == trk_item) { + already_loaded = true; + break; + } + } + if (!already_loaded) { + tracks.resize(tracks.size() + 1); + tracks.back().open(trk_item, true); + } + } + } + } else if (!tracks.empty() && !opts.genome_tag.empty()) { + std::string &genome_tag = opts.genome_tag; + tracks.erase(std::remove_if(tracks.begin(), tracks.end(), + [&genome_tag] (HGW::GwTrack &trk) { return (trk.genome_tag == genome_tag); } ), + tracks.end()); + } + pool.reset(opts.threads); + } + void GwPlot::updateSettings() { mode = last_mode; redraw = true; @@ -624,6 +689,7 @@ namespace Manager { fonts = Themes::Fonts(); fonts.setTypeface(opts.font_str, opts.font_size); std::ostream& outerr = (terminalOutput) ? std::cerr : outStr; + if (opts.myIni.get("genomes").has(opts.genome_tag) && reference != opts.myIni["genomes"][opts.genome_tag]) { faidx_t *fai_test = fai_load( opts.myIni["genomes"][opts.genome_tag].c_str()); if (fai_test != nullptr) { @@ -728,9 +794,9 @@ namespace Manager { Utils::Region ®ion = regions[regionSelection]; if (key == opts.scroll_right) { int shift = (int)(((float)region.end - (float)region.start) * opts.scroll_speed); - if (region.refSeq != nullptr) { - delete region.refSeq; - } +// if (region.refSeq != nullptr) { +// delete region.refSeq; +// } region.start = std::max(0, region.start + shift); region.end = std::max(region.start + 1, region.end + shift); fetchRefSeq(region); @@ -764,9 +830,9 @@ namespace Manager { if (shift == 0) { return; } - if (region.refSeq != nullptr) { - delete region.refSeq; - } +// if (region.refSeq != nullptr) { +// delete region.refSeq; +// } region.start = std::max(0, region.start - shift); region.end = std::max(region.start + 1, region.end - shift); fetchRefSeq(region); @@ -801,9 +867,9 @@ namespace Manager { if (shift == 0) { return; } - if (region.refSeq != nullptr) { - delete region.refSeq; - } +// if (region.refSeq != nullptr) { +// delete region.refSeq; +// } region.start = std::max(0, region.start - shift_left); region.end = std::max(region.start + 1, region.end + shift); fetchRefSeq(region); @@ -854,9 +920,9 @@ namespace Manager { } else if (key == opts.zoom_in) { if (region.end - region.start > 50) { int shift = (int)(((float)region.end - (float)region.start) * opts.scroll_speed); - if (region.refSeq != nullptr) { - delete region.refSeq; - } +// if (region.refSeq != nullptr) { +// delete region.refSeq; +// } region.start = std::max(0, region.start + shift); region.end = std::max(region.start + 1, region.end - shift); fetchRefSeq(region); @@ -1012,7 +1078,7 @@ namespace Manager { } } - void GwPlot::addTrack(std::string &path, bool print_message=true) { + void GwPlot::addTrack(std::string &path, bool print_message=true, bool vcf_as_track=false, bool bed_as_track=false) { std::ostream& out = (terminalOutput) ? std::cout : outStr; bool good = false; if (Utils::endsWith(path, ".bam") || Utils::endsWith(path, ".cram")) { @@ -1028,7 +1094,8 @@ namespace Manager { headers.push_back(hdr_ptr); hts_idx_t* idx = sam_index_load(f, path.c_str()); indexes.push_back(idx); - } else if (!opts.vcf_as_tracks && (Utils::endsWith(path, ".vcf.gz") || Utils::endsWith(path, ".vcf") || Utils::endsWith(path, ".bcf"))) { + } else if ((!vcf_as_track && (Utils::endsWith(path, ".vcf.gz") || Utils::endsWith(path, ".vcf") || Utils::endsWith(path, ".bcf"))) + || (!bed_as_track && (Utils::endsWith(path, ".bed") || Utils::endsWith(path, ".bed.gz")))){ good = true; std::vector labels = Utils::split(opts.labels, ','); setLabelChoices(labels); @@ -1042,8 +1109,10 @@ namespace Manager { mode = Manager::Show::TILED; if (print_message) { - out << termcolor::magenta << "\nFile " << termcolor::reset << variantTracks[variantFileSelection].path << "\n"; + out << termcolor::magenta << "\nFile " << termcolor::reset + << variantTracks[variantFileSelection].path << "\n"; } + } else { tracks.push_back(HGW::GwTrack()); try { @@ -1052,6 +1121,7 @@ namespace Manager { if (print_message) { out << termcolor::magenta << "\nTrack " << termcolor::reset << path << "\n"; } + good = true; } catch (...) { tracks.pop_back(); } @@ -1059,6 +1129,8 @@ namespace Manager { if (good) { processed = false; redraw = true; + } else { + redraw = false; } } @@ -1317,7 +1389,7 @@ namespace Manager { region.start = std::max(0, strt); region.end = std::max(region.start + 1, strt + 5000); regionSelection = cl.regionIdx; - delete region.refSeq; +// delete region.refSeq; fetchRefSeq(region); processed = false; redraw = true; @@ -1422,7 +1494,7 @@ namespace Manager { if (region.start < 0 || region.end < 0) { return; } - delete region.refSeq; +// delete region.refSeq; fetchRefSeq(region); bool lt_last = region.start < old_start; @@ -1516,9 +1588,9 @@ namespace Manager { rt[0].start = (posX - 10000 > 0) ? posX - 100000 : 1; rt[0].end = posX + 100000; } - for (auto &r: regions) { - delete r.refSeq; - } +// for (auto &r: regions) { +// delete r.refSeq; +// } regions.clear(); regions = rt; redraw = true; @@ -1622,7 +1694,6 @@ namespace Manager { } } else if (mode == Manager::SETTINGS && button == GLFW_MOUSE_BUTTON_LEFT && action == GLFW_RELEASE) { bool keep_alive = Menu::navigateMenu(opts, GLFW_KEY_ENTER, GLFW_PRESS, inputText, &charIndex, &captureText, &textFromSettings, &processText, reference); - redraw = true; if (opts.editing_underway) { textFromSettings = true; @@ -1782,7 +1853,7 @@ namespace Manager { if (region.start < 1 || region.end < 1) { return; } - delete region.refSeq; +// delete region.refSeq; fetchRefSeq(region); for (auto &cl : collections) { if (cl.regionIdx == regionSelection) { diff --git a/src/plot_manager.cpp b/src/plot_manager.cpp index cf35ab1..2d1b672 100644 --- a/src/plot_manager.cpp +++ b/src/plot_manager.cpp @@ -160,7 +160,9 @@ namespace Manager { for (auto &idx: indexes) { hts_idx_destroy(idx); } - fai_destroy(fai); +// if (fai != nullptr) { +// fai_destroy(fai); +// } } void ErrorCallback(int, const char* err_str) { @@ -289,17 +291,19 @@ namespace Manager { void GwPlot::fetchRefSeq(Utils::Region &rgn) { int rlen = rgn.end - rgn.start - 1; - rgn.refSeq = faidx_fetch_seq(fai, rgn.chrom.c_str(), rgn.start, rgn.end - 1, &rlen); + if (rlen < opts.snp_threshold || rlen < 20000) { + rgn.refSeq = faidx_fetch_seq(fai, rgn.chrom.c_str(), rgn.start, rgn.end - 1, &rlen); + } + if (rgn.chromLength == 0) { + rgn.chromLength = faidx_seq_len(fai, rgn.chrom.c_str()); + } } void GwPlot::fetchRefSeqs() { for (auto &rgn : regions) { - if (rgn.end - rgn.start < opts.snp_threshold) { + if (rgn.end - rgn.start < opts.snp_threshold || rgn.end - rgn.start < 20000) { fetchRefSeq(rgn); } - if (rgn.chromLength == 0) { - rgn.chromLength = faidx_seq_len(fai, rgn.chrom.c_str()); - } } } @@ -334,7 +338,7 @@ namespace Manager { std::shared_ptr> sLabels = std::make_shared>(seenLabels[variantFilename]); variantTracks.push_back( - HGW::GwVariantTrack(path, cacheStdin, &opts, startIndex + (opts.number.x * opts.number.y), + HGW::GwVariantTrack(path, cacheStdin, &opts, startIndex, labelChoices, inLabels, sLabels) @@ -349,11 +353,10 @@ namespace Manager { if (outLabelFile.empty()) { return; } -// std::cout << "Saving labels to file: " << outLabelFile << std::endl; std::string dateStr = Utils::dateTime(); std::ofstream f; f.open(outLabelFile); - f << "#chrom\tpos\tvariant_ID\tlabel\tvar_type\tlabelled_date\tvariant_filename\n"; + f << "#chrom\tpos\tvariant_ID\tlabel\tvar_type\tlabelled_date\tvariant_filename\tcomment\n"; for (auto &vf : variantTracks) { std::string fileName; if (vf.type != HGW::TrackType::IMAGES) { @@ -553,6 +556,10 @@ namespace Manager { outStr << "Type '/help' for more info\n"; } +#if !defined(__EMSCRIPTEN__) + Term::startVersionCheck(); +#endif + vCursor = glfwCreateStandardCursor(GLFW_VRESIZE_CURSOR); setGlfwFrameBufferSize(); @@ -848,9 +855,10 @@ namespace Manager { frameId += 1; setGlfwFrameBufferSize(); + if (regions.empty()) { setScaling(); - + canvasR->drawPaint(opts.theme.bgPaint); } else { processBam(); setScaling(); @@ -860,6 +868,10 @@ namespace Manager { } SkRect clip; + if (collections.empty()) { + canvasR->drawPaint(opts.theme.bgPaint); + } + for (auto &cl: collections) { if (cl.skipDrawingCoverage && cl.skipDrawingReads) { // keep read and coverage area continue; @@ -867,7 +879,7 @@ namespace Manager { canvasR->save(); // for now cl.skipDrawingCoverage and cl.skipDrawingReads are almost always the same - if ((!cl.skipDrawingCoverage && !cl.skipDrawingReads) || imageCacheQueue.empty()) { + if ((!cl.skipDrawingCoverage && !cl.skipDrawingReads)) {// || imageCacheQueue.empty()) { if (cl.bamIdx == 0) { // cover the ref too clip.setXYWH(cl.xOffset, 0, cl.regionPixels, cl.yOffset + trackY + covY + refSpace); } else { @@ -875,12 +887,12 @@ namespace Manager { } canvasR->clipRect(clip, false); } else if (cl.skipDrawingCoverage) { - clip.setXYWH(cl.xOffset, cl.yOffset + refSpace, cl.regionPixels, cl.yPixels - covY); + clip.setXYWH(cl.xOffset, cl.yOffset, cl.regionPixels, cl.yPixels); canvasR->clipRect(clip, false); - } else if (cl.skipDrawingReads){ // skip reads + } else if (cl.skipDrawingReads){ clip.setXYWH(cl.xOffset, cl.yOffset - covY, cl.regionPixels, covY); canvasR->clipRect(clip, false); - } + } // else no clip canvasR->drawPaint(opts.theme.bgPaint); if (!cl.skipDrawingReads) { @@ -1392,26 +1404,33 @@ namespace Manager { SkRect clip; for (auto &cl: collections) { - if (cl.skipDrawingCoverage && cl.skipDrawingReads) { // keep read and coverage area - continue; - } +// if (cl.skipDrawingCoverage && cl.skipDrawingReads) { // keep read and coverage area +// continue; +// } canvas->save(); // for now cl.skipDrawingCoverage and cl.skipDrawingReads are almost always the same - if ((!cl.skipDrawingCoverage && !cl.skipDrawingReads) || imageCacheQueue.empty()) { - if (cl.bamIdx == 0) { // cover the ref too - clip.setXYWH(cl.xOffset, 0, cl.regionPixels, cl.yOffset + trackY + covY + refSpace); - } else { - clip.setXYWH(cl.xOffset, cl.yOffset - covY, cl.regionPixels, cl.yOffset + trackY + covY); - } - canvas->clipRect(clip, false); - } else if (cl.skipDrawingCoverage) { - clip.setXYWH(cl.xOffset, cl.yOffset + refSpace, cl.regionPixels, cl.yPixels - covY); - canvas->clipRect(clip, false); - } else if (cl.skipDrawingReads){ // skip reads - clip.setXYWH(cl.xOffset, cl.yOffset - covY, cl.regionPixels, covY); - canvas->clipRect(clip, false); +// if ((!cl.skipDrawingCoverage && !cl.skipDrawingReads) || imageCacheQueue.empty()) { +// if (cl.bamIdx == 0) { // cover the ref too +// clip.setXYWH(cl.xOffset, 0, cl.regionPixels, cl.yOffset + trackY + covY + refSpace); +// } else { +// clip.setXYWH(cl.xOffset, cl.yOffset - covY, cl.regionPixels, cl.yOffset + trackY + covY); +// } +// canvas->clipRect(clip, false); +// } else if (cl.skipDrawingCoverage) { +// clip.setXYWH(cl.xOffset, cl.yOffset + refSpace, cl.regionPixels, cl.yPixels - covY); +// canvas->clipRect(clip, false); +// } else if (cl.skipDrawingReads){ // skip reads +// clip.setXYWH(cl.xOffset, cl.yOffset - covY, cl.regionPixels, covY); +// canvas->clipRect(clip, false); +// } + + if (cl.bamIdx == 0) { // cover the ref too + clip.setXYWH(cl.xOffset, 0, cl.regionPixels, cl.yOffset + trackY + covY + refSpace); + } else { + clip.setXYWH(cl.xOffset, cl.yOffset - covY, cl.regionPixels, cl.yOffset + trackY + covY); } + canvas->clipRect(clip, false); canvas->drawPaint(opts.theme.bgPaint); if (!cl.skipDrawingReads) { @@ -1436,7 +1455,6 @@ namespace Manager { } canvas->restore(); - // Drawing::drawCollection(opts, cl, canvas, trackY, yScaling, fonts, opts.link_op, refSpace, pointSlop, textDrop, pH, monitorScale); } if (opts.max_coverage) { diff --git a/src/plot_manager.h b/src/plot_manager.h index 9112841..26fab05 100644 --- a/src/plot_manager.h +++ b/src/plot_manager.h @@ -158,11 +158,13 @@ namespace Manager { void rasterToPng(const char* path); + void loadGenome(std::string genome_tag_or_path, std::ostream& outerr); + void addBam(std::string &bam_path); void removeBam(int index); - void addTrack(std::string &path, bool print_message); + void addTrack(std::string &path, bool print_message, bool vcf_as_track, bool bed_as_track); void removeTrack(int index); diff --git a/src/term_out.cpp b/src/term_out.cpp index 6fe3edd..74badc3 100644 --- a/src/term_out.cpp +++ b/src/term_out.cpp @@ -48,7 +48,7 @@ namespace Term { out << termcolor::green << "insertions, ins " << termcolor::reset << "Toggle insertions\n"; out << termcolor::green << "line " << termcolor::reset << "Toggle mouse position vertical line\n"; out << termcolor::green << "link none/sv/all " << termcolor::reset << "Switch read-linking 'link all'\n"; - out << termcolor::green << "load file " << termcolor::reset << "Load bams, tracks or session file\n"; + out << termcolor::green << "load type? file " << termcolor::reset << "Load bams, tracks, tiles, session file or ideogram\n"; out << termcolor::green << "log2-cov " << termcolor::reset << "Toggle scale coverage by log2\n"; out << termcolor::green << "mate add? " << termcolor::reset << "Use 'mate' to navigate to mate-pair, or 'mate add' \n to add a new region with mate \n"; out << termcolor::green << "mismatches, mm " << termcolor::reset << "Toggle mismatches\n"; @@ -58,7 +58,7 @@ namespace Term { out << termcolor::green << "remove, rm index " << termcolor::reset << "Remove a region by index e.g. 'rm 1'. To remove a bam \n use the bam index 'rm bam1', or track 'rm track1'\n"; out << termcolor::green << "roi region? name? " << termcolor::reset << "Add a region of interest\n"; out << termcolor::green << "sam " << termcolor::reset << "Print selected read in sam format\n"; - out << termcolor::green << "save filename " << termcolor::reset << "Save reads (.bam/.cram), snapshot (.png) or session (.ini) to file\n"; + out << termcolor::green << "save filename " << termcolor::reset << "Save reads (bam/cram), snapshot (png), session (ini), or labels (tsv/txt)\n"; out << termcolor::green << "settings " << termcolor::reset << "Open the settings menu'\n"; out << termcolor::green << "snapshot, s path? " << termcolor::reset << "Save current window to png e.g. 's', or 's view.png',\n or vcf columns can be used 's {pos}_{info.SU}.png'\n"; out << termcolor::green << "soft-clips, sc " << termcolor::reset << "Toggle soft-clips\n"; @@ -190,17 +190,22 @@ namespace Term { } else if (s == "link" || s == "l") { out << " Link alignments.\n This will change how alignments are linked, options are 'none', 'sv', 'all'.\n Examples:\n 'link sv', 'link all'\n\n"; } else if (s == "load") { - out << " Load reads, tracks or session file.\n" - " The filepath extension will determine which type of file to load.\n\n" + out << " Load reads, tracks, tiles, session file or ideogram.\n" + " The type identifier is optional, and if not supplied then the filepath extension will.\n" + " will determine which type of file to load.\n\n" " Examples:\n" " 'load reads.bam' # Load reads.bam file.\n" " 'load reads.cram' # Load a cram file\n" " 'load repeats.bed' # Load a bed file\n" " 'load variants.vcf' # Load a vcf file\n" " 'load session.xml' # Load a previous session\n\n" + " 'load bam a.bam' # Load alignments (bam, cram)\n" + " 'load track a.bed' # Load a track (bed, vcf/bcf, gtf/gff3)\n" + " 'load tiled a.bed' # Load a file to generate image-tiled from (vcf, bed)\n\n" + " 'load ideogram a.bed' # Load an ideogram file (bed)\n\n" " Notes:\n" - " Vcfs/bcfs can be loaded as a track or image tiles. Control this behavior using the\n" - " settings option Settings -> Interaction -> vcf_as_tracks" + " Vcfs/bcfs/beds can be loaded as a track or image tiles. Control this behavior using the\n" + " settings option Settings -> Interaction -> vcf_as_tracks and bed_as_tracks" "\n\n"; } else if (s == "log2-cov") { out << " Toggle log2-coverage.\n The coverage track will be scaled by log2.\n\n"; @@ -219,15 +224,16 @@ namespace Term { } else if (s == "sam") { out << " Print the sam format of the read.\n First select a read using the mouse then type ':sam'.\n\n"; } else if (s == "save") { - out << " Save reads, snapshot or session to file.\n" + out << " Save reads, snapshot, session file, or labels file.\n" " The filepath extension will determine the output file type.\n\n" " Examples:\n" " 'save reads.bam' # Save visible reads to reads.bam file.\n" - " 'save reads.bam [0, 1]' # Indexing can be used, here reads from row 0, column 1 will be saved\n" + " 'save reads.bam [0, 1]' # Indexing can be used - here reads from row 0, column 1 will be saved\n" " 'save reads.cram' # Reads saved in cram format\n" - " 'save reads.sam' # Reads saved in sam format (human readable)\n" + " 'save reads.sam' # Reads saved in sam format (human readable)\n\n" " 'save view.png' # The current view is saved to view.png. Same functionality as 'snapshot'\n" - " 'save session.ini' # The current session will be saved, allowing this session to be revisited\n\n" + " 'save session.ini' # The current session will be saved, allowing this session to be revisited\n" + " 'save labels.tsv' # The output label file will be saved here\n\n" " Notes:\n" " Any read-filters are applied when saving reads\n" " Reads are saved in sorted order, however issues may arise if different bam headers\n" @@ -1141,6 +1147,19 @@ namespace Term { out << v; term_space -= (int)v.size(); out << std::flush; + + if (label->comment.empty()) { + return; + } + + v = " " + label->comment; + if ((int)v.size() < term_space) { + out << v << std::flush; + } else if (term_space > 10) { + v.erase(v.begin(), v.begin() + term_space); + out << v << std::flush; + } + } int check_url(const char *url) { @@ -1315,4 +1334,58 @@ namespace Term { } } +#if !defined(__EMSCRIPTEN__) + const char* CURRENT_VERSION = "v0.10.0"; + + size_t WriteCallback(void* contents, size_t size, size_t nmemb, void* userp) { + ((std::string*)userp)->append((char*)contents, size * nmemb); + return size * nmemb; + } + + std::string getLatestVersion() { + CURL* curl; + CURLcode res; + std::string readBuffer; + + curl = curl_easy_init(); + if (curl) { + struct curl_slist* headers = nullptr; + headers = curl_slist_append(headers, "User-Agent: gw-app"); + + curl_easy_setopt(curl, CURLOPT_URL, "https://api.github.com/repos/kcleal/gw/tags"); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer); + res = curl_easy_perform(curl); + curl_easy_cleanup(curl); + curl_slist_free_all(headers); + + if (res == CURLE_OK) { + // Simple JSON parsing to find the latest version + size_t pos = readBuffer.find("\"name\""); + if (pos != std::string::npos) { + size_t start = readBuffer.find("\"", pos + 7) + 1; + size_t end = readBuffer.find("\"", start); + return readBuffer.substr(start, end - start); + } + } + } + return ""; + } + + void checkVersion() { + std::string latestVersion = getLatestVersion(); + if (!latestVersion.empty()) { + if (latestVersion != CURRENT_VERSION) { + std::cout << "\nVersion " << latestVersion << " is available: " << "https://github.com/kcleal/gw" << std::endl; + } + } + } + + void startVersionCheck() { + std::thread versionCheckThread(checkVersion); + versionCheckThread.detach(); // Detach the thread to run independently + } +#endif + } diff --git a/src/term_out.h b/src/term_out.h index e276d38..263d65b 100644 --- a/src/term_out.h +++ b/src/term_out.h @@ -50,4 +50,6 @@ namespace Term { std::ostream& out); void updateRefGenomeSeq(Utils::Region *region, float xW, float xOffset, float xScaling, std::ostream& out); + + void startVersionCheck(); } diff --git a/src/themes.cpp b/src/themes.cpp index 3327035..4d8738a 100644 --- a/src/themes.cpp +++ b/src/themes.cpp @@ -392,6 +392,7 @@ namespace Themes { tlen_yscale = false; expand_tracks = false; vcf_as_tracks = false; + bed_as_tracks = true; sv_arcs = true; parse_mods = true; @@ -507,6 +508,9 @@ namespace Themes { if (myIni["interaction"].has("vcf_as_tracks")) { vcf_as_tracks = myIni["interaction"]["vcf_as_tracks"] == "true"; } + if (myIni["interaction"].has("bed_as_tracks")) { + bed_as_tracks = myIni["interaction"]["bed_as_tracks"] == "true"; + } number_str = myIni["labelling"]["number"]; number = Utils::parseDimensions(number_str); @@ -720,7 +724,7 @@ namespace Themes { output_session = myIni["general"]["session_file"]; } mINI::INIFile file(output_session); -// seshIni["data"].clear(); + seshIni.clear(); seshIni["data"]["genome_tag"] = genome_tag; seshIni["data"]["genome_path"] = genome_path; diff --git a/src/themes.h b/src/themes.h index 3653b68..8de9506 100644 --- a/src/themes.h +++ b/src/themes.h @@ -148,7 +148,7 @@ namespace Themes { bool editing_underway; int canvas_width, canvas_height; int indel_length, ylim, split_view_size, threads, pad, link_op, max_coverage, max_tlen, mods_qual_threshold; - bool no_show, log2_cov, tlen_yscale, expand_tracks, vcf_as_tracks, sv_arcs, parse_mods; + bool no_show, log2_cov, tlen_yscale, expand_tracks, vcf_as_tracks, bed_as_tracks, sv_arcs, parse_mods; float scroll_speed, tab_track_height; int scroll_right; int scroll_left; diff --git a/src/utils.cpp b/src/utils.cpp index 298c780..43ca512 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -427,13 +427,14 @@ namespace Utils { } Label makeLabel(std::string &chrom, int pos, std::string &parsed, std::vector &inputLabels, std::string &variantId, std::string &vartype, - std::string savedDate, bool clicked, bool add_empty_label=false) { + std::string savedDate, bool clicked, bool add_empty_label, std::string& comment) { Label l; l.chrom = chrom; l.pos = pos; l.variantId = variantId; l.vartype = vartype; l.savedDate = savedDate; + l.comment = comment; l.i = 0; l.ori_i = 0; l.clicked = clicked; @@ -492,14 +493,14 @@ namespace Utils { void labelToFile(std::ofstream &f, Utils::Label &l, std::string &dateStr, std::string &variantFileName) { f << l.chrom << "\t" << l.pos << "\t" << l.variantId << "\t" << l.current() << "\t" << l.vartype << "\t" << (((l.contains_parsed_label && l.i == l.ori_i) || (!l.contains_parsed_label && l.i > 0)) ? l.savedDate : dateStr) << - "\t" << variantFileName << + "\t" << variantFileName << "\t" << l.comment << std::endl; } void saveLabels(Utils::Label &l, std::ofstream &fileOut, std::string &dateStr, std::string &variantFileName) { fileOut << l.chrom << "\t" << l.pos << "\t" << l.variantId << "\t" << l.current() << "\t" << l.vartype << "\t" << (((l.contains_parsed_label && l.i == l.ori_i) || (!l.contains_parsed_label && l.i > 0)) ? l.savedDate : dateStr) << - "\t" << variantFileName << + "\t" << variantFileName << "\t" << l.comment << std::endl; } @@ -532,7 +533,11 @@ namespace Utils { if (!seenLabels[variantFilename].contains(v->at(3))) { seenLabels[variantFilename].insert(v->at(3)); } - Label l = makeLabel(v->at(0), pos, v->at(3), inputLabels, v->at(2), v->at(4), savedDate, clicked); + std::string comment; + if (v->size() >= 8) { + comment = v->at(7); + } + Label l = makeLabel(v->at(0), pos, v->at(3), inputLabels, v->at(2), v->at(4), savedDate, clicked, false, comment); std::string key; key = v->at(2); diff --git a/src/utils.h b/src/utils.h index b833c1d..cdca37b 100644 --- a/src/utils.h +++ b/src/utils.h @@ -127,7 +127,7 @@ namespace Utils { public: Label() = default; ~Label() = default; - std::string chrom, variantId, savedDate, vartype; + std::string chrom, variantId, savedDate, vartype, comment; std::vector labels; int i, pos, ori_i; bool clicked; @@ -141,7 +141,7 @@ namespace Utils { std::string dateTime(); Label makeLabel(std::string &chrom, int pos, std::string &parsed, std::vector &inputLabels, std::string &variantId, std::string &vartype, - std::string savedDate, bool clicked, bool add_empty_label); + std::string savedDate, bool clicked, bool add_empty_label, std::string& comment); void labelToFile(std::ofstream &f, Utils::Label &l, std::string &dateStr, std::string &variantFileName); diff --git a/test/vaf.vcf b/test/vaf.vcf new file mode 100644 index 0000000..ea1ddc4 --- /dev/null +++ b/test/vaf.vcf @@ -0,0 +1,23 @@ +##fileformat=VCFv4.1 +##reference=file:///seq/references/1000GenomesPilot-NCBI36.fasta +##contig= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##FILTER= +##FILTER= +##FORMAT= +##FORMAT= +##FORMAT= +##FORMAT= +##FORMAT= +#CHROM POS ID REF ALT QUAL FILTER INFO FORMAT NA00001 NA00002 NA00003 +chr20 14370 rs6054257 G A 29.1 . NS=3;DP=14;AF=0.5;HOMSEQ;DB GT:GQ:DP:HQ:CNL 0|0:48:1:25,30:10,20 1|0:48:8:49,51:. ./.:43:5:.,.:1 +chr20 17330 . T A . q10;s50 NS=3;DP=11;AF=0.017;H2 GT:GQ:DP:HQ 0|0:49:3:58,50 0|1:3:5:65,3 0/0:41:3:4,5 +chr20 1110696 rs6040355 A G,T 67 PASS NS=2;DP=10;AF=0.333,0.667;AA=T;DB GT:GQ:DP:HQ 1|2:21:6:23,27 2|1:2:0:18,2 2/2:35:4:10,20 +chr20 1230237 . T . 47 PASS NS=3;DP=13;AA=T GT:GQ:DP:HQ 0|0:54:7:56,60 0|0:48:4:51,51 ./. +chr20 1234567 microsat1 GTC G,GTCT 50 PASS NS=3;DP=9;AA=G GT:GQ:DP 0/1:35:4 0/2:17:2 1/1:40:3 \ No newline at end of file From 6c9b98e0f46162e403709236a0039279cab6681f Mon Sep 17 00:00:00 2001 From: kcleal Date: Thu, 11 Jul 2024 21:25:43 +0100 Subject: [PATCH 3/5] Label comments now have orange dot. Fixed segfaults OOB for refseq --- src/drawing.cpp | 14 ++++++++++++++ src/plot_commands.cpp | 6 +++--- src/plot_manager.cpp | 5 +++++ src/utils.cpp | 4 ++-- src/utils.h | 2 ++ 5 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/drawing.cpp b/src/drawing.cpp index 361ba85..6efb078 100644 --- a/src/drawing.cpp +++ b/src/drawing.cpp @@ -212,6 +212,7 @@ namespace Drawing { } int i = 0; + int refSeqLen = cl.region->refSeqLen; for (const auto &mm: mmVector) { float cum_h = 0; float mm_h; @@ -251,6 +252,9 @@ namespace Drawing { continue; } const SkPaint *paint_ref; + if (i >= refSeqLen) { + break; + } switch (refSeq[i]) { case 'A': paint_ref = &theme.fcA; @@ -501,6 +505,7 @@ namespace Drawing { uint8_t *ptr_qual = bam_get_qual(align.delegate); const char *refSeq = region->refSeq; + int refSeqLen = region->refSeqLen; float precalculated_xOffset_mmPosOffset = xOffset + mmPosOffset; @@ -527,6 +532,9 @@ namespace Drawing { size_t ref_idx = pos_start - region->start; for (size_t i=idx_start; i < (size_t)idx_end; ++i) { + if (i >= refSeqLen) { + break; + } char ref_base = lookup_ref_base[(unsigned char)refSeq[ref_idx]]; char bam_base = bam_seqi(ptr_seq, i); if (bam_base != ref_base) { @@ -1398,8 +1406,14 @@ namespace Drawing { } if (label.i != label.ori_i) { + canvas->drawRect(rect, opts.theme.lcJoins); } + if (!label.comment.empty()) { + bg.setXYWH(rect.right() - fonts.overlayHeight - (4*pad), rect.bottom() - fonts.overlayHeight - pad - pad - pad - pad, fonts.overlayHeight, + fonts.overlayHeight); + canvas->drawRoundRect(bg, fonts.overlayHeight, fonts.overlayHeight, opts.theme.fcG); + } } void drawTrackBigWig(HGW::GwTrack &trk, const Utils::Region &rgn, SkRect &rect, float padX, float padY, diff --git a/src/plot_commands.cpp b/src/plot_commands.cpp index ea197f5..c461bc5 100644 --- a/src/plot_commands.cpp +++ b/src/plot_commands.cpp @@ -1076,7 +1076,7 @@ namespace Commands { if (parts.size() == 3) { filename = Parse::tilde_to_home(parts.back()); - std::string ext = std::filesystem::path(filename).extension(); + std::string ext = std::filesystem::path(filename).extension().string(); if (std::filesystem::is_directory(filename)) { out << termcolor::red << "Error:" << termcolor::reset << " This is a folder path, not a file\n"; return Err::SILENT; @@ -1324,7 +1324,7 @@ namespace Commands { b.start = r.start; b.end = r.end; good = true; - s_idx += parts[1].size(); + s_idx += (int)parts[1].size(); } } catch (...) { } @@ -1425,7 +1425,7 @@ namespace Commands { out << command << std::endl; if (p->mode == Manager::Show::TILED && !p->variantTracks.empty() && p->currentVarTrack != nullptr) { int i = p->mouseOverTileIndex + p->currentVarTrack->blockStart; - if (i < p->currentVarTrack->multiLabels.size()) { + if (i < (int)p->currentVarTrack->multiLabels.size()) { p->currentVarTrack->multiLabels[i].comment = command; } } diff --git a/src/plot_manager.cpp b/src/plot_manager.cpp index 2d1b672..5165c3c 100644 --- a/src/plot_manager.cpp +++ b/src/plot_manager.cpp @@ -297,6 +297,11 @@ namespace Manager { if (rgn.chromLength == 0) { rgn.chromLength = faidx_seq_len(fai, rgn.chrom.c_str()); } + if (rgn.end <= rgn.chromLength) { + rgn.refSeqLen = rgn.end - rgn.start; + } else { + rgn.refSeqLen = rgn.chromLength - rgn.start; + } } void GwPlot::fetchRefSeqs() { diff --git a/src/utils.cpp b/src/utils.cpp index 43ca512..91dafaa 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -418,8 +418,8 @@ namespace Utils { b.yStart = (h * (float) y) + padY + ySpace; b.xEnd = b.xStart + w - padX; b.yEnd = b.yStart + h - padY; - b.width = w - padX * 2; - b.height = h - padY * 2; + b.width = w - padX;// * 2; + b.height = h - padY;// * 2; ++i; } } diff --git a/src/utils.h b/src/utils.h index cdca37b..ec9a3ce 100644 --- a/src/utils.h +++ b/src/utils.h @@ -77,6 +77,7 @@ namespace Utils { int start, end; int markerPos, markerPosEnd; int chromLength; + int refSeqLen; const char *refSeq; std::vector refSeq_nibbled; std::vector> featuresInView; // one vector for each Track @@ -89,6 +90,7 @@ namespace Utils { markerPosEnd = -1; chromLength = 0; refSeq = nullptr; + refSeqLen = 0; } std::string toString(); }; From 5554fcac0b8a107ae6a9057570d7d2a311640f4a Mon Sep 17 00:00:00 2001 From: kcleal Date: Fri, 12 Jul 2024 10:55:05 +0100 Subject: [PATCH 4/5] Fixed long-standing zero division bug. Fixed screen clipping and screen refresh issues --- src/drawing.cpp | 8 +- src/plot_commands.cpp | 23 +++- src/plot_controls.cpp | 2 +- src/plot_manager.cpp | 260 +++++++++++++++++++++++++----------------- src/term_out.cpp | 16 +++ src/utils.h | 5 +- 6 files changed, 199 insertions(+), 115 deletions(-) diff --git a/src/drawing.cpp b/src/drawing.cpp index 6efb078..a080f4d 100644 --- a/src/drawing.cpp +++ b/src/drawing.cpp @@ -1877,8 +1877,8 @@ namespace Drawing { float regionIdx = 0; for (const auto& region: regions) { - float s = (float)region.start / (float)region.chromLength; - float e = (float)region.end / (float)region.chromLength; + float s = (float)region.start / (float)region.chromLen; + float e = (float)region.end / (float)region.chromLen; float w = (e - s) * drawWidth; if (w < 3) { w = 3; @@ -1894,8 +1894,8 @@ namespace Drawing { if (it != ideogram.end()) { const std::vector& bands = it->second; for (const auto& b : bands) { - float sb = (float) b.start / (float)region.chromLength; - float eb = (float) b.end / (float)region.chromLength; + float sb = (float) b.start / (float)region.chromLen; + float eb = (float) b.end / (float)region.chromLen; float wb = (eb - sb) * drawWidth; rect.setXYWH(xp + (sb * drawWidth), top + yh_one_third, diff --git a/src/plot_commands.cpp b/src/plot_commands.cpp index c461bc5..de2cc93 100644 --- a/src/plot_commands.cpp +++ b/src/plot_commands.cpp @@ -759,7 +759,7 @@ namespace Commands { if (res <= 0) { return Err::CHROM_NOT_IN_REFERENCE; } else { - dummy_region.chromLength = faidx_seq_len(p->fai, dummy_region.chrom.c_str()); + dummy_region.chromLen = faidx_seq_len(p->fai, dummy_region.chrom.c_str()); new_regions.push_back(dummy_region); p->fetchRefSeq(new_regions.back()); } @@ -1078,15 +1078,18 @@ namespace Commands { filename = Parse::tilde_to_home(parts.back()); std::string ext = std::filesystem::path(filename).extension().string(); if (std::filesystem::is_directory(filename)) { + p->redraw = true; out << termcolor::red << "Error:" << termcolor::reset << " This is a folder path, not a file\n"; return Err::SILENT; } if (parts[1] == "ideogram") { if (!std::filesystem::exists(parts.back())) { + p->redraw = true; return Err::INVALID_PATH; } if (ext != ".bed") { out << termcolor::red << "Error:" << termcolor::reset << " Only .bed extension supported for ideograms\n"; + p->redraw = true; return Err::SILENT; } else { p->addIdeogram(filename); @@ -1095,6 +1098,7 @@ namespace Commands { } } else if (parts[1] == "track") { if (!std::filesystem::exists(parts.back())) { + p->redraw = true; return Err::INVALID_PATH; } if (ext == ".bam" || ext == ".cram") { @@ -1108,6 +1112,7 @@ namespace Commands { } else if (parts[1] == "tiled") { if (!std::filesystem::exists(parts.back())) { + p->redraw = true; return Err::INVALID_PATH; } if (ext == ".vcf" || ext == ".vcf.gz" || ext == ".bcf" || ext == ".bed" || ext == ".bed.gz") { @@ -1116,10 +1121,12 @@ namespace Commands { return Err::NONE; } else { out << termcolor::red << "Error:" << termcolor::reset << " Image tiling only supported for .vcf|.vcf.gz|.bcf|.bed|.bed.gz file extensions\n"; + p->redraw = true; return Err::SILENT; } } else if (parts[1] == "bam" || parts[1] == "cram") { if (!std::filesystem::exists(parts.back())) { + p->redraw = true; return Err::INVALID_PATH; } p->addTrack(filename, true, p->opts.vcf_as_tracks, p->opts.bed_as_tracks); @@ -1131,6 +1138,7 @@ namespace Commands { return Err::NONE; } else if (parts[1] == "labels") { if (!std::filesystem::exists(parts.back())) { + p->redraw = true; return Err::INVALID_PATH; } p->seenLabels.clear(); @@ -1172,6 +1180,7 @@ namespace Commands { refreshGw(p); return Err::NONE; } + p->redraw = true; return Err::INVALID_PATH; } filename = Parse::tilde_to_home(parts.back()); @@ -1197,7 +1206,7 @@ namespace Commands { if (p->regions.empty()) { p->regions.push_back(rgn); p->fetchRefSeq(p->regions.back()); - p->regions.back().chromLength = faidx_seq_len(p->fai, p->regions.back().chrom.c_str()); + p->regions.back().chromLen = faidx_seq_len(p->fai, p->regions.back().chrom.c_str()); } else { if (p->regions[p->regionSelection].chrom == rgn.chrom) { rgn.markerPos = p->regions[p->regionSelection].markerPos; @@ -1205,7 +1214,7 @@ namespace Commands { } p->regions[p->regionSelection] = rgn; p->fetchRefSeq(p->regions[p->regionSelection]); - p->regions[p->regionSelection].chromLength = faidx_seq_len(p->fai, p->regions[p->regionSelection].chrom.c_str()); + p->regions[p->regionSelection].chromLen = faidx_seq_len(p->fai, p->regions[p->regionSelection].chrom.c_str()); } } else { // search all tracks for matching name, slow but ok for small tracks if (!p->tracks.empty()) { @@ -1406,8 +1415,8 @@ namespace Commands { out << termcolor::red << "Error:" << termcolor::reset << " Input could not be parsed\n"; break; } + p->redraw = false; if (p->mode == Manager::Show::SINGLE) { - p->redraw = false; for (auto &cl : p->collections) { cl.skipDrawingReads = true; cl.skipDrawingCoverage = true; @@ -1427,6 +1436,12 @@ namespace Commands { int i = p->mouseOverTileIndex + p->currentVarTrack->blockStart; if (i < (int)p->currentVarTrack->multiLabels.size()) { p->currentVarTrack->multiLabels[i].comment = command; + p->redraw = true; + p->processed = false; + for (auto &cl : p->collections) { + cl.skipDrawingReads = false; + cl.skipDrawingCoverage = false; + }; } } command = ""; diff --git a/src/plot_controls.cpp b/src/plot_controls.cpp index 5e10ed1..6d40010 100644 --- a/src/plot_controls.cpp +++ b/src/plot_controls.cpp @@ -428,6 +428,7 @@ namespace Manager { return col.bamIdx == index; }), collections.end()); bams.erase(bams.begin() + index, bams.begin() + index + 1); + bam_paths.erase(bam_paths.begin() + index, bam_paths.begin() + index + 1); indexes.erase(indexes.begin() + index, indexes.begin() + index + 1); headers.erase(headers.begin() + index, headers.begin() + index + 1); processed = false; @@ -620,7 +621,6 @@ namespace Manager { void GwPlot::loadGenome(std::string genome_tag_or_path, std::ostream& outerr) { if (opts.myIni.get("genomes").has(genome_tag_or_path) && reference != opts.myIni["genomes"][genome_tag_or_path]) { - std::cout << opts.myIni["genomes"][opts.genome_tag] << std::endl; faidx_t *fai_test = fai_load(opts.myIni["genomes"][genome_tag_or_path].c_str()); if (fai_test != nullptr) { reference = opts.myIni["genomes"][genome_tag_or_path]; diff --git a/src/plot_manager.cpp b/src/plot_manager.cpp index 5165c3c..2e7736e 100644 --- a/src/plot_manager.cpp +++ b/src/plot_manager.cpp @@ -49,14 +49,14 @@ namespace Manager { void HiddenWindow::init(int width, int height) { if (!glfwInit()) { - std::cerr<< "ERROR: could not initialize GLFW3" < &bampaths, Themes::IniOptions &opt, std::vector ®ions, - std::vector &track_paths) { + EXPORT GwPlot::GwPlot(std::string reference, std::vector &bampaths, Themes::IniOptions &opt, + std::vector ®ions, + std::vector &track_paths) { this->reference = reference; this->bam_paths = bampaths; this->regions = regions; @@ -93,20 +94,20 @@ namespace Manager { } } for (auto &fn: bampaths) { - htsFile* f = sam_open(fn.c_str(), "r"); + htsFile *f = sam_open(fn.c_str(), "r"); hts_set_fai_filename(f, reference.c_str()); hts_set_threads(f, opt.threads); bams.push_back(f); sam_hdr_t *hdr_ptr = sam_hdr_read(f); headers.push_back(hdr_ptr); - hts_idx_t* idx = sam_index_load(f, fn.c_str()); + hts_idx_t *idx = sam_index_load(f, fn.c_str()); indexes.push_back(idx); } if (!opts.genome_tag.empty() && !opts.ini_path.empty() && opts.myIni["tracks"].has(opts.genome_tag)) { std::vector track_paths_temp = Utils::split(opts.myIni["tracks"][opts.genome_tag], ','); tracks.reserve(track_paths.size() + track_paths_temp.size()); - for (const auto &trk_item : track_paths_temp) { + for (const auto &trk_item: track_paths_temp) { if (!Utils::is_file_exist(trk_item)) { if (terminalOutput) { std::cerr << "Warning: track file does not exists - " << trk_item << std::endl; @@ -151,7 +152,7 @@ namespace Manager { glfwDestroyWindow(backWindow); } glfwTerminate(); - for (auto &bm : bams) { + for (auto &bm: bams) { hts_close(bm); } for (auto &hd: headers) { @@ -165,17 +166,18 @@ namespace Manager { // } } - void ErrorCallback(int, const char* err_str) { - std::cout << "GLFW Error: " << err_str << std::endl; + void ErrorCallback(int, const char *err_str) { + std::cerr << "GLFW Error: " << err_str << std::endl; } int GwPlot::makeRasterSurface() { - SkImageInfo info = SkImageInfo::MakeN32Premul(opts.dimensions.x * monitorScale, opts.dimensions.y * monitorScale); + SkImageInfo info = SkImageInfo::MakeN32Premul(opts.dimensions.x * monitorScale, + opts.dimensions.y * monitorScale); size_t rowBytes = info.minRowBytes(); size_t size = info.computeByteSize(rowBytes); this->pixelMemory.resize(size); this->rasterSurface = SkSurface::MakeRasterDirect( - info, &pixelMemory[0], rowBytes); + info, &pixelMemory[0], rowBytes); rasterCanvas = rasterSurface->getCanvas(); rasterSurfacePtr = &rasterSurface; return pixelMemory.size(); @@ -217,42 +219,42 @@ namespace Manager { std::cerr << "ERROR: glfwCreateWindow failed\n"; std::exit(-1); } - #if defined(_WIN32) || defined(_WIN64) - GLFWimage iconimage; - getWindowIconImage(&iconimage); - glfwSetWindowIcon(window, 1, &iconimage); - #endif +#if defined(_WIN32) || defined(_WIN64) + GLFWimage iconimage; + getWindowIconImage(&iconimage); + glfwSetWindowIcon(window, 1, &iconimage); +#endif // https://stackoverflow.com/questions/7676971/pointing-to-a-function-that-is-a-class-member-glfw-setkeycallback/28660673#28660673 glfwSetWindowUserPointer(window, this); - auto func_key = [](GLFWwindow* w, int k, int s, int a, int m){ - static_cast(glfwGetWindowUserPointer(w))->keyPress(k, s, a, m); + auto func_key = [](GLFWwindow *w, int k, int s, int a, int m) { + static_cast(glfwGetWindowUserPointer(w))->keyPress(k, s, a, m); }; glfwSetKeyCallback(window, func_key); - auto func_drop = [](GLFWwindow* w, int c, const char**paths){ - static_cast(glfwGetWindowUserPointer(w))->pathDrop(c, paths); + auto func_drop = [](GLFWwindow *w, int c, const char **paths) { + static_cast(glfwGetWindowUserPointer(w))->pathDrop(c, paths); }; glfwSetDropCallback(window, func_drop); - auto func_mouse = [](GLFWwindow* w, int b, int a, int m){ - static_cast(glfwGetWindowUserPointer(w))->mouseButton(b, a, m); + auto func_mouse = [](GLFWwindow *w, int b, int a, int m) { + static_cast(glfwGetWindowUserPointer(w))->mouseButton(b, a, m); }; glfwSetMouseButtonCallback(window, func_mouse); - auto func_pos = [](GLFWwindow* w, double x, double y){ - static_cast(glfwGetWindowUserPointer(w))->mousePos(x, y); + auto func_pos = [](GLFWwindow *w, double x, double y) { + static_cast(glfwGetWindowUserPointer(w))->mousePos(x, y); }; glfwSetCursorPosCallback(window, func_pos); - auto func_scroll = [](GLFWwindow* w, double x, double y){ - static_cast(glfwGetWindowUserPointer(w))->scrollGesture(x, y); + auto func_scroll = [](GLFWwindow *w, double x, double y) { + static_cast(glfwGetWindowUserPointer(w))->scrollGesture(x, y); }; glfwSetScrollCallback(window, func_scroll); - auto func_resize = [](GLFWwindow* w, int x, int y){ - static_cast(glfwGetWindowUserPointer(w))->windowResize(x, y); + auto func_resize = [](GLFWwindow *w, int x, int y) { + static_cast(glfwGetWindowUserPointer(w))->windowResize(x, y); }; glfwSetWindowSizeCallback(window, func_resize); @@ -289,37 +291,39 @@ namespace Manager { setGlfwFrameBufferSize(); } - void GwPlot::fetchRefSeq(Utils::Region &rgn) { - int rlen = rgn.end - rgn.start - 1; - if (rlen < opts.snp_threshold || rlen < 20000) { - rgn.refSeq = faidx_fetch_seq(fai, rgn.chrom.c_str(), rgn.start, rgn.end - 1, &rlen); - } - if (rgn.chromLength == 0) { - rgn.chromLength = faidx_seq_len(fai, rgn.chrom.c_str()); - } - if (rgn.end <= rgn.chromLength) { - rgn.refSeqLen = rgn.end - rgn.start; + void GwPlot::fetchRefSeq(Utils::Region & rgn) { + // This will be called for every new region visited + assert (rgn.start >= 0); + rgn.regionLen = rgn.end - rgn.start; + if (rgn.chromLen == 0) { + rgn.chromLen = faidx_seq_len(fai, rgn.chrom.c_str()); + } + if (rgn.regionLen < opts.snp_threshold || rgn.regionLen < 20000) { + rgn.refSeq = faidx_fetch_seq(fai, rgn.chrom.c_str(), rgn.start, rgn.end - 1, &rgn.regionLen - 1); + if (rgn.end <= rgn.chromLen) { + rgn.refSeqLen = rgn.end - rgn.start; + } else { + rgn.refSeqLen = rgn.chromLen - rgn.start; + } } else { - rgn.refSeqLen = rgn.chromLength - rgn.start; + rgn.refSeqLen = 0; } } void GwPlot::fetchRefSeqs() { - for (auto &rgn : regions) { - if (rgn.end - rgn.start < opts.snp_threshold || rgn.end - rgn.start < 20000) { - fetchRefSeq(rgn); - } + for (auto &rgn: regions) { + fetchRefSeq(rgn); } } void GwPlot::addBam(std::string &bam_path) { - htsFile* f = sam_open(bam_path.c_str(), "r"); + htsFile *f = sam_open(bam_path.c_str(), "r"); hts_set_fai_filename(f, reference.c_str()); hts_set_threads(f, opts.threads); bams.push_back(f); sam_hdr_t *hdr_ptr = sam_hdr_read(f); headers.push_back(hdr_ptr); - hts_idx_t* idx = sam_index_load(f, bam_path.c_str()); + hts_idx_t *idx = sam_index_load(f, bam_path.c_str()); indexes.push_back(idx); } @@ -339,8 +343,12 @@ namespace Manager { variantFilename = path; } - std::shared_ptr> inLabels = std::make_shared>(inputLabels[variantFilename]); - std::shared_ptr> sLabels = std::make_shared>(seenLabels[variantFilename]); + std::shared_ptr> + inLabels = std::make_shared> + (inputLabels[variantFilename]); + std::shared_ptr> + sLabels = std::make_shared> + (seenLabels[variantFilename]); variantTracks.push_back( HGW::GwVariantTrack(path, cacheStdin, &opts, startIndex, @@ -362,7 +370,7 @@ namespace Manager { std::ofstream f; f.open(outLabelFile); f << "#chrom\tpos\tvariant_ID\tlabel\tvar_type\tlabelled_date\tvariant_filename\tcomment\n"; - for (auto &vf : variantTracks) { + for (auto &vf: variantTracks) { std::string fileName; if (vf.type != HGW::TrackType::IMAGES) { std::filesystem::path fsp(vf.path); @@ -376,7 +384,7 @@ namespace Manager { #endif } int i = 0; - for (auto &l : vf.multiLabels) { + for (auto &l: vf.multiLabels) { if (vf.type == HGW::TrackType::IMAGES) { std::filesystem::path fsp(vf.image_glob[i]); #if defined(_WIN32) || defined(_WIN64) @@ -454,7 +462,11 @@ namespace Manager { reference = opts.seshIni["data"]["genome_path"]; opts.genome_tag = opts.seshIni["data"]["genome_tag"]; if (opts.seshIni["data"].has("ideogram_path")) { - addIdeogram(opts.seshIni["data"]["ideogram_path"]); + if (std::filesystem::exists(opts.seshIni["data"]["ideogram_path"])) { + addIdeogram(opts.seshIni["data"]["ideogram_path"]); + } else { + std::cerr << "Ideogram file does not exists: " << opts.seshIni["data"]["ideogram_path"] << std::endl; + } } if (opts.seshIni["data"].has("mode")) { mode = (opts.seshIni["data"]["mode"] == "tiled") ? Show::TILED : Show::SINGLE; @@ -473,9 +485,23 @@ namespace Manager { setLabelChoices(labels); } - bam_paths.clear(); tracks.clear(); regions.clear(); filters.clear(); variantTracks.clear(); - bams.clear(); headers.clear(); indexes.clear(); collections.clear(); - for (const auto& item : opts.seshIni["data"]) { + bam_paths.clear(); + tracks.clear(); + regions.clear(); + filters.clear(); + variantTracks.clear(); + bams.clear(); + headers.clear(); + indexes.clear(); + collections.clear(); + for (const auto &item: opts.seshIni["data"]) { + if (Utils::startsWith(item.first, "ideogram") || Utils::startsWith(item.first, "genome")) { + continue; + } + if (!std::filesystem::exists(item.second)) { + std::cerr << item.first << " data file does not exists: " << item.second << std::endl; + continue; + } if (Utils::startsWith(item.first, "bam")) { bam_paths.push_back(item.second); } else if (Utils::startsWith(item.first, "track")) { @@ -491,7 +517,7 @@ namespace Manager { } } size_t count = 0; - for (const auto& item : opts.seshIni["show"]) { + for (const auto &item: opts.seshIni["show"]) { if (Utils::startsWith(item.first, "region")) { std::string rgn = item.second; regions.push_back(Utils::parseRegion(rgn)); @@ -507,13 +533,13 @@ namespace Manager { fonts.setTypeface(opts.font_str, opts.font_size); for (auto &fn: bam_paths) { - htsFile* f = sam_open(fn.c_str(), "r"); + htsFile *f = sam_open(fn.c_str(), "r"); hts_set_fai_filename(f, reference.c_str()); hts_set_threads(f, opts.threads); bams.push_back(f); sam_hdr_t *hdr_ptr = sam_hdr_read(f); headers.push_back(hdr_ptr); - hts_idx_t* idx = sam_index_load(f, fn.c_str()); + hts_idx_t *idx = sam_index_load(f, fn.c_str()); indexes.push_back(idx); } if (opts.seshIni.has("commands")) { @@ -531,32 +557,33 @@ namespace Manager { redraw = true; } - void GwPlot::saveSession(std::string output_session="") { + void GwPlot::saveSession(std::string output_session = "") { std::vector track_paths; - for (const auto& item: tracks) { + for (const auto &item: tracks) { if (item.kind != HGW::FType::ROI) { track_paths.push_back(item.path); } } std::vector> variant_paths_info; - for (const auto& item: variantTracks) { + for (const auto &item: variantTracks) { variant_paths_info.push_back({item.path, item.blockStart}); } int xpos, ypos; glfwGetWindowPos(window, &xpos, &ypos); int windX, windY; glfwGetWindowSize(window, &windX, &windY); - opts.saveCurrentSession(reference, ideogram_path, bam_paths, track_paths, regions, variant_paths_info, commandsApplied, + opts.saveCurrentSession(reference, ideogram_path, bam_paths, track_paths, regions, variant_paths_info, + commandsApplied, output_session, mode, xpos, ypos, monitorScale, windX, windY); } - int GwPlot::startUI(GrDirectContext* sContext, SkSurface *sSurface, int delay) { + int GwPlot::startUI(GrDirectContext *sContext, SkSurface *sSurface, int delay) { if (!opts.session_file.empty() && reference.empty()) { std::cout << "Loading session: " << opts.session_file << std::endl; loadSession(); } if (terminalOutput) { - std::cerr << "\nType" << termcolor::green << " '/help'" << termcolor::reset << " for more info\n"; + std::cout << "\nType" << termcolor::green << " '/help'" << termcolor::reset << " for more info\n"; } else { outStr << "Type '/help' for more info\n"; } @@ -570,14 +597,15 @@ namespace Manager { fetchRefSeqs(); opts.theme.setAlphas(); - GLFWwindow * wind = this->window; + GLFWwindow *wind = this->window; if (mode == Show::SINGLE) { printRegionInfo(); } else { mouseOverTileIndex = 0; - bboxes = Utils::imageBoundingBoxes(opts.number, (float)fb_width, (float)fb_height); + bboxes = Utils::imageBoundingBoxes(opts.number, (float) fb_width, (float) fb_height); if (terminalOutput) { - std::cout << termcolor::magenta << "File " << termcolor::reset << variantTracks[variantFileSelection].path << "\n"; + std::cout << termcolor::magenta << "File " << termcolor::reset + << variantTracks[variantFileSelection].path << "\n"; } else { outStr << "File " << variantTracks[variantFileSelection].path << "\n"; } @@ -610,14 +638,15 @@ namespace Manager { // sSurface->getCanvas()->drawImage(imageCacheQueue.back().second, 0, 0); // } - if (resizeTriggered && std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - resizeTimer) > 100ms) { + if (resizeTriggered && std::chrono::duration_cast( + std::chrono::high_resolution_clock::now() - resizeTimer) > 100ms) { imageCache.clear(); redraw = true; processed = false; wasResized = true; setGlfwFrameBufferSize(); setScaling(); - bboxes = Utils::imageBoundingBoxes(opts.number, (float)fb_width, (float)fb_height); + bboxes = Utils::imageBoundingBoxes(opts.number, (float) fb_width, (float) fb_height); resizeTriggered = false; @@ -643,18 +672,23 @@ namespace Manager { nullptr, nullptr).release(); if (!sSurface) { - std::cerr << "ERROR: sSurface could not be initialized (nullptr). The frame buffer format needs changing\n"; + std::cerr + << "ERROR: sSurface could not be initialized (nullptr). The frame buffer format needs changing\n"; std::exit(-1); } - rasterSurface = SkSurface::MakeRasterN32Premul(fb_width,fb_height); + rasterSurface = SkSurface::MakeRasterN32Premul(fb_width, fb_height); rasterCanvas = rasterSurface->getCanvas(); rasterSurfacePtr = &rasterSurface; + imageCache.clear(); + imageCacheQueue.clear(); + resizeTimer = std::chrono::high_resolution_clock::now(); } - if (std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - autoSaveTimer) > 1min) { + if (std::chrono::duration_cast( + std::chrono::high_resolution_clock::now() - autoSaveTimer) > 1min) { saveLabels(); autoSaveTimer = std::chrono::high_resolution_clock::now(); } @@ -675,8 +709,8 @@ namespace Manager { void GwPlot::clearCollections() { regions.clear(); - for (auto & cl : collections) { - for (auto & a : cl.readQueue) { + for (auto &cl: collections) { + for (auto &a: cl.readQueue) { bam_destroy1(a.delegate); } } @@ -684,12 +718,12 @@ namespace Manager { } void GwPlot::processBam() { // collect reads, calc coverage and find y positions on plot - const int parse_mods_threshold = (opts.parse_mods) ? opts.mods_qual_threshold: 0; + const int parse_mods_threshold = (opts.parse_mods) ? opts.mods_qual_threshold : 0; if (!processed) { int idx = 0; for (auto &cl: collections) { - for (auto &aln : cl.readQueue) { + for (auto &aln: cl.readQueue) { bam_destroy1(aln.delegate); } cl.readQueue.clear(); @@ -705,19 +739,20 @@ namespace Manager { collections.resize(bams.size() * regions.size()); } - for (int i=0; i<(int)bams.size(); ++i) { - htsFile* b = bams[i]; + for (int i = 0; i < (int) bams.size(); ++i) { + htsFile *b = bams[i]; sam_hdr_t *hdr_ptr = headers[i]; hts_idx_t *index = indexes[i]; - for (int j=0; j<(int)regions.size(); ++j) { - Utils::Region *reg = ®ions[j]; - if (collections[idx].skipDrawingReads) { - continue; - } + for (int j = 0; j < (int) regions.size(); ++j) { + Utils::Region * reg = ®ions[j]; + collections[idx].bamIdx = i; collections[idx].regionIdx = j; collections[idx].region = ®ions[j]; + if (collections[idx].skipDrawingReads) { + continue; + } if (opts.max_coverage) { collections[idx].covArr.resize(reg->end - reg->start + 1, 0); if (opts.snp_threshold > reg->end - reg->start) { @@ -729,8 +764,10 @@ namespace Manager { } } if (reg->end - reg->start < opts.low_memory || opts.link_op != 0) { - HGW::collectReadsAndCoverage(collections[idx], b, hdr_ptr, index, opts.threads, reg, (bool)opts.max_coverage, filters, pool, parse_mods_threshold); - int maxY = Segs::findY(collections[idx], collections[idx].readQueue, opts.link_op, opts, reg, false); + HGW::collectReadsAndCoverage(collections[idx], b, hdr_ptr, index, opts.threads, reg, + (bool) opts.max_coverage, filters, pool, parse_mods_threshold); + int maxY = Segs::findY(collections[idx], collections[idx].readQueue, opts.link_op, opts, reg, + false); if (maxY > samMaxY) { samMaxY = maxY; } @@ -744,7 +781,7 @@ namespace Manager { if (bams.empty()) { collections.resize(regions.size()); - for (int j=0; j<(int)regions.size(); ++j) { + for (int j = 0; j < (int) regions.size(); ++j) { collections[idx].bamIdx = -1; collections[idx].regionIdx = j; collections[idx].region = ®ions[j]; @@ -768,20 +805,20 @@ namespace Manager { monitorScale = 1; glfwGetFramebufferSize(backWindow, &fb_width, &fb_height); } - gap = std::fmax(5, fb_height * 0.01 * monitorScale); + gap = std::fmin(std::fmax(5, fb_height * 0.01 * monitorScale), monitorScale * 10); } void GwPlot::setRasterSize(int width, int height) { - monitorScale = 1; +// monitorScale = 1; fb_width = width; fb_height = height; - gap = std::fmax(5, fb_height * 0.01 * monitorScale); + gap = std::fmin(std::fmax(5, fb_height * 0.01 * monitorScale), monitorScale * 10); } void GwPlot::setScaling() { // sets z_scaling, y_scaling trackY and regionWidth fonts.setOverlayHeight(monitorScale); refSpace = fonts.overlayHeight * 1.25; - sliderSpace = std::fmax((float)(fb_height * 0.0175), 10*monitorScale); //refSpace + (gap * 0.5); // + gap + gap; + sliderSpace = std::fmax((float)(fb_height * 0.0175), 10*monitorScale); auto fbh = (float) fb_height; auto fbw = (float) fb_width; if (bams.empty()) { @@ -800,7 +837,7 @@ namespace Manager { totalTabixY = (fbh - refSpace - sliderSpace) * (float)opts.tab_track_height; tabixY = totalTabixY / (float)tracks.size(); } - if (nbams > 0) { + if (nbams > 0 && samMaxY > 0) { trackY = (fbh - totalCovY - totalTabixY - refSpace - sliderSpace) / nbams; yScaling = ( (fbh - totalCovY - totalTabixY - refSpace - sliderSpace - (gap * nbams)) / (double)samMaxY) / nbams; @@ -811,6 +848,7 @@ namespace Manager { trackY = 0; yScaling = 0; } + fonts.setFontSize(yScaling, monitorScale); regionWidth = fbw / (float)regions.size(); bamHeight = covY + trackY; @@ -861,12 +899,14 @@ namespace Manager { frameId += 1; setGlfwFrameBufferSize(); - if (regions.empty()) { - setScaling(); + if (regions.empty() || bams.empty()) { canvasR->drawPaint(opts.theme.bgPaint); } else { processBam(); setScaling(); + if (yScaling == 0) { + return; + } if (!imageCacheQueue.empty() && collections.size() > 1) { canvasR->drawImage(imageCacheQueue.back().second, 0, 0); @@ -884,11 +924,15 @@ namespace Manager { canvasR->save(); // for now cl.skipDrawingCoverage and cl.skipDrawingReads are almost always the same - if ((!cl.skipDrawingCoverage && !cl.skipDrawingReads)) {// || imageCacheQueue.empty()) { - if (cl.bamIdx == 0) { // cover the ref too + if ((!cl.skipDrawingCoverage && !cl.skipDrawingReads) || imageCacheQueue.empty()) { + if (bams.size() == 1) { + clip.setXYWH(cl.xOffset, 0, cl.regionPixels, cl.yOffset + trackY + totalTabixY + covY + refSpace); + } else if (cl.bamIdx == 0) { // top bam, cover the ref too clip.setXYWH(cl.xOffset, 0, cl.regionPixels, cl.yOffset + trackY + covY + refSpace); - } else { - clip.setXYWH(cl.xOffset, cl.yOffset - covY, cl.regionPixels, cl.yOffset + trackY + covY); + } else if (cl.bamIdx == (int)bams.size() - 1) { // bottom bam + clip.setXYWH(cl.xOffset, cl.yOffset - covY, cl.regionPixels, cl.yOffset + trackY + covY + totalTabixY); + } else { //middle bam + clip.setXYWH(cl.xOffset, cl.yOffset - covY, cl.regionPixels, cl.yOffset + covY); } canvasR->clipRect(clip, false); } else if (cl.skipDrawingCoverage) { @@ -974,7 +1018,7 @@ namespace Manager { if (xPos_fb < xp || xPos_fb > xp + drawWidth) { return; } - int pos = (int)(((xPos_fb - xp) / drawWidth) * (float)regions[regionSelection].chromLength); + int pos = (int)(((xPos_fb - xp) / drawWidth) * (float)regions[regionSelection].chromLen); std::string s = Term::intToStringCommas(pos); float estimatedTextWidth = (float) s.size() * fonts.overlayWidth; @@ -1002,7 +1046,7 @@ namespace Manager { } // slider overlay - if (mode == Show::SINGLE) { + if (mode == Show::SINGLE && bams.size() > 0) { drawCursorPosOnRefSlider(canvas); } @@ -1405,6 +1449,9 @@ namespace Manager { fetchRefSeqs(); processBam(); setScaling(); + if (yScaling == 0) { + return; + } canvas->drawPaint(opts.theme.bgPaint); SkRect clip; for (auto &cl: collections) { @@ -1524,19 +1571,24 @@ namespace Manager { // for now cl.skipDrawingCoverage and cl.skipDrawingReads are almost always the same if ((!cl.skipDrawingCoverage && !cl.skipDrawingReads) || imageCacheQueue.empty()) { - if (cl.bamIdx == 0) { // cover the ref too + if (bams.size() == 1) { + clip.setXYWH(cl.xOffset, 0, cl.regionPixels, cl.yOffset + trackY + totalTabixY + covY + refSpace); + } else if (cl.bamIdx == 0) { // top bam, cover the ref too clip.setXYWH(cl.xOffset, 0, cl.regionPixels, cl.yOffset + trackY + covY + refSpace); - } else { - clip.setXYWH(cl.xOffset, cl.yOffset - covY, cl.regionPixels, cl.yOffset + trackY + covY); + } else if (cl.bamIdx == (int)bams.size() - 1) { // bottom bam + clip.setXYWH(cl.xOffset, cl.yOffset - covY, cl.regionPixels, cl.yOffset + trackY + covY + totalTabixY); + } else { //middle bam + clip.setXYWH(cl.xOffset, cl.yOffset - covY, cl.regionPixels, cl.yOffset + covY); } canvas->clipRect(clip, false); } else if (cl.skipDrawingCoverage) { - clip.setXYWH(cl.xOffset, cl.yOffset + refSpace, cl.regionPixels, cl.yPixels - covY); + clip.setXYWH(cl.xOffset, cl.yOffset, cl.regionPixels, cl.yPixels); canvas->clipRect(clip, false); - } else if (cl.skipDrawingReads){ // skip reads + } else if (cl.skipDrawingReads){ clip.setXYWH(cl.xOffset, cl.yOffset - covY, cl.regionPixels, covY); canvas->clipRect(clip, false); - } + } // else no clip + canvas->drawPaint(opts.theme.bgPaint); if (!cl.skipDrawingReads) { diff --git a/src/term_out.cpp b/src/term_out.cpp index 74badc3..6a79247 100644 --- a/src/term_out.cpp +++ b/src/term_out.cpp @@ -770,6 +770,12 @@ namespace Term { float min_x = xOffset; float max_x = xScaling * ((float)(region->end - region->start)) + min_x; int size = region->end - region->start; + + if (region->end - region->start > region->refSeqLen) { + if (region->end < region->chromLen) { + return; // refseq not fully loaded from region. user needs to zoom in to fetch refseq + } + } if (x > min_x && x < max_x && size <= 20000) { const char * s = region->refSeq; out << "\n\n" << region->chrom << ":" << region->start << "-" << region->end << "\n"; @@ -1288,6 +1294,16 @@ namespace Term { } void updateRefGenomeSeq(Utils::Region *region, float xW, float xOffset, float xScaling, std::ostream& out) { + if (region->refSeqLen == 0) { + return; + } + + if ((region->end - region->start) < region->refSeqLen) { + if (region->end < region->chromLen) { + return; // refseq not fully loaded from region. user needs to zoom in to fetch refseq + } + } + float min_x = xOffset; float max_x = xScaling * ((float)(region->end - region->start)) + min_x; if (xW > min_x && xW < max_x) { diff --git a/src/utils.h b/src/utils.h index ec9a3ce..6fe2c1b 100644 --- a/src/utils.h +++ b/src/utils.h @@ -76,8 +76,9 @@ namespace Utils { std::string chrom; int start, end; int markerPos, markerPosEnd; - int chromLength; + int chromLen; int refSeqLen; + int regionLen; const char *refSeq; std::vector refSeq_nibbled; std::vector> featuresInView; // one vector for each Track @@ -88,7 +89,7 @@ namespace Utils { end = -1; markerPos = -1; markerPosEnd = -1; - chromLength = 0; + chromLen = 0; refSeq = nullptr; refSeqLen = 0; } From 1642994b705c8e10a069a6047f8a74f72d50ebd7 Mon Sep 17 00:00:00 2001 From: kcleal Date: Fri, 12 Jul 2024 12:02:43 +0100 Subject: [PATCH 5/5] Variant track comments in SINGLE mode also. More bug fixes. --- src/drawing.cpp | 6 ++++-- src/plot_commands.cpp | 2 +- src/plot_controls.cpp | 18 +++++++++++++++--- src/plot_manager.cpp | 9 +++++---- 4 files changed, 25 insertions(+), 10 deletions(-) diff --git a/src/drawing.cpp b/src/drawing.cpp index a080f4d..508f943 100644 --- a/src/drawing.cpp +++ b/src/drawing.cpp @@ -532,7 +532,7 @@ namespace Drawing { size_t ref_idx = pos_start - region->start; for (size_t i=idx_start; i < (size_t)idx_end; ++i) { - if (i >= refSeqLen) { + if ((int)i >= refSeqLen) { break; } char ref_base = lookup_ref_base[(unsigned char)refSeq[ref_idx]]; @@ -941,13 +941,15 @@ namespace Drawing { } } - } else { + } else if (nBlocks == 1) { s = (double)a.blocks[0].start - regionBegin; e = (double)a.blocks[0].end - regionBegin; width = (e - s) * xScaling; drawBlock(plotPointedPolygons, pointLeft, edged, (float) s * xScaling, (float) e, (float) width, pointSlop, pH, yScaledOffset, xOffset, regionPixels, nBlocks, regionLen, a, canvas, path, rect, faceColor, edgeColor); + } else { + continue; } // add soft-clip blocks diff --git a/src/plot_commands.cpp b/src/plot_commands.cpp index de2cc93..183b7bb 100644 --- a/src/plot_commands.cpp +++ b/src/plot_commands.cpp @@ -1432,7 +1432,7 @@ namespace Commands { if (Utils::startsWith(command, "'") || Utils::startsWith(command, "\"")) { command.erase(command.begin(), command.begin() + 1); out << command << std::endl; - if (p->mode == Manager::Show::TILED && !p->variantTracks.empty() && p->currentVarTrack != nullptr) { + if (!p->variantTracks.empty() && p->currentVarTrack != nullptr) { int i = p->mouseOverTileIndex + p->currentVarTrack->blockStart; if (i < (int)p->currentVarTrack->multiLabels.size()) { p->currentVarTrack->multiLabels[i].comment = command; diff --git a/src/plot_controls.cpp b/src/plot_controls.cpp index 6d40010..1963039 100644 --- a/src/plot_controls.cpp +++ b/src/plot_controls.cpp @@ -103,7 +103,7 @@ namespace Manager { if (variantTracks.empty()) { return GLFW_KEY_UNKNOWN; } - + assert (variantFileSelection < variantTracks.size()); currentVarTrack = &variantTracks[variantFileSelection]; if (currentVarTrack == nullptr) { return key; @@ -123,6 +123,7 @@ namespace Manager { processed = false; imageCacheQueue.clear(); if (currentVarTrack->blockStart < (int)currentVarTrack->multiRegions.size()) { + assert (!currentVarTrack->multiRegions[currentVarTrack->blockStart].empty()); if (currentVarTrack->multiRegions[currentVarTrack->blockStart][0].chrom.empty()) { return key; // check for "" no chrom set } else { @@ -1787,7 +1788,7 @@ namespace Manager { commandToolTipIndex = -1; } - float trackBoundary = totalCovY + refSpace + (trackY*(float)headers.size()); + float trackBoundary = totalCovY + refSpace + (trackY*(float)headers.size()) + (gap * 0.5); if (!tracks.empty()) { if (std::fabs(yPos_fb - trackBoundary) < 5 * monitorScale) { glfwSetCursor(window, vCursor); @@ -1939,8 +1940,14 @@ namespace Manager { return; } int targetIndex = (rs * -1) -3; + if (targetIndex >= (int)tracks.size()) { + return; + } HGW::GwTrack &targetTrack = tracks[targetIndex]; float stepY = (totalTabixY) / (float)tracks.size(); + if (regionSelection >= (int)regions.size() || targetIndex >= (int)regions[regionSelection].featureLevels.size()) { + return; + } float step_track = (stepY) / ((float)regions[regionSelection].featureLevels[targetIndex]); float y = fb_height - totalTabixY - refSpace; // start of tracks on canvas int featureLevel = (int)(yPos_fb - y - (targetIndex * stepY)) / step_track; @@ -1948,6 +1955,9 @@ namespace Manager { } } if (rs < 0) { // print reference info + if (regionSelection >= (int)regions.size()) { + return; + } float xScaling = (float)((regionWidth - gap - gap) / ((double)(regions[regionSelection].end -regions[regionSelection].start))); float xOffset = (regionWidth * (float)regionSelection) + gap; if (rs == REFERENCE_TRACK) { @@ -1968,7 +1978,7 @@ namespace Manager { } return; } - + assert (rs < collections.size()); Segs::ReadCollection &cl = collections[rs]; regionSelection = cl.regionIdx; int pos = (int) ((((double)xPos_fb - (double)cl.xOffset) / (double)cl.xScaling) + (double)cl.region->start); @@ -1981,7 +1991,9 @@ namespace Manager { } updateCursorGenomePos(cl.xOffset, cl.xScaling, (float)xPos_fb, cl.region, cl.bamIdx); } else if (mode == TILED) { + assert (variantFileSelection < variantTracks.size()); currentVarTrack = &variantTracks[variantFileSelection]; + assert (currentVarTrack != nullptr); int i = 0; for (auto &b: bboxes) { if (xPos_fb > b.xStart && xPos_fb < b.xEnd && yPos_fb > b.yStart && yPos_fb < b.yEnd) { diff --git a/src/plot_manager.cpp b/src/plot_manager.cpp index 2e7736e..af66566 100644 --- a/src/plot_manager.cpp +++ b/src/plot_manager.cpp @@ -818,7 +818,7 @@ namespace Manager { void GwPlot::setScaling() { // sets z_scaling, y_scaling trackY and regionWidth fonts.setOverlayHeight(monitorScale); refSpace = fonts.overlayHeight * 1.25; - sliderSpace = std::fmax((float)(fb_height * 0.0175), 10*monitorScale); + sliderSpace = std::fmax((float)(fb_height * 0.0175), 10*monitorScale) + (gap * 0.5); auto fbh = (float) fb_height; auto fbw = (float) fb_width; if (bams.empty()) { @@ -839,11 +839,11 @@ namespace Manager { } if (nbams > 0 && samMaxY > 0) { trackY = (fbh - totalCovY - totalTabixY - refSpace - sliderSpace) / nbams; - - yScaling = ( (fbh - totalCovY - totalTabixY - refSpace - sliderSpace - (gap * nbams)) / (double)samMaxY) / nbams; + yScaling = (trackY - (gap * nbams)) / (double)samMaxY; if (yScaling > 3 * monitorScale) { yScaling = (int)yScaling; } + } else { trackY = 0; yScaling = 0; @@ -1384,7 +1384,7 @@ namespace Manager { setGlfwFrameBufferSize(); setScaling(); float y_gap = (variantTracks.size() <= 1) ? 0 : (10 * monitorScale); - bboxes = Utils::imageBoundingBoxes(opts.number, fb_width, fb_height, 15, 15, y_gap); + bboxes = Utils::imageBoundingBoxes(opts.number, fb_width, fb_height, 6 * monitorScale, 6 * monitorScale, y_gap); if (currentVarTrack->image_glob.empty()) { tileDrawingThread(canvas, sContext, sSurface); // draws images from variant file } else { @@ -1515,6 +1515,7 @@ namespace Manager { Drawing::drawRef(opts, regions, fb_width, canvas, fonts, refSpace, (float)regions.size(), gap); Drawing::drawBorders(opts, fb_width, fb_height, canvas, regions.size(), bams.size(), trackY, covY, (int)tracks.size(), totalTabixY, refSpace, gap); Drawing::drawTracks(opts, fb_width, fb_height, canvas, totalTabixY, tabixY, tracks, regions, fonts, gap, monitorScale); + Drawing::drawChromLocation(opts, regions, ideogram, canvas, fai, fb_width, fb_height, monitorScale); } void GwPlot::runDraw() {