From 9f1bbce54c758bea4da14d3214f58b59e7071c4a Mon Sep 17 00:00:00 2001 From: kcleal Date: Fri, 23 Feb 2024 22:11:24 +0000 Subject: [PATCH 01/51] Added shared library build option --- .gitignore | 1 + Makefile | 4 ++-- include/export_definitions.h | 19 +++++++++++++++++++ src/themes.h | 3 ++- 4 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 include/export_definitions.h diff --git a/.gitignore b/.gitignore index a43cd25..0d3dc91 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,4 @@ /lib/libBigWig/bwValues.o /lib/libBigWig/bwWrite.o /lib/libBigWig/io.o +/libgw/ diff --git a/Makefile b/Makefile index ca0066d..f9fc56c 100644 --- a/Makefile +++ b/Makefile @@ -147,5 +147,5 @@ shared: CXXFLAGS += -fPIC shared: $(OBJECTS) -mkdir -p libgw/include -cp src/*.h libgw/include - -cp include/*.h libgw/include - $(CXX) $(OBJECTS) $(LDFLAGS) $(LDLIBS) -shared -o $(SHARED_TARGET) + -cp include/*.h* libgw/include + $(CXX) $(OBJECTS) $(LDFLAGS) $(LDLIBS) -shared -DBUILDING_LIBGW -o $(SHARED_TARGET) diff --git a/include/export_definitions.h b/include/export_definitions.h new file mode 100644 index 0000000..4478ad6 --- /dev/null +++ b/include/export_definitions.h @@ -0,0 +1,19 @@ +// +// Created by Kez Cleal on 23/02/2024. +// + +#pragma once + +#if defined _WIN32 || defined __CYGWIN__ + #ifdef BUILDING_LIBGW + #define EXPORT __declspec(dllexport) + #else + #define EXPORT __declspec(dllimport) + #endif +#else + #if __GNUC__ >= 4 + #define EXPORT __attribute__ ((visibility ("default"))) + #else + #define EXPORT + #endif +#endif diff --git a/src/themes.h b/src/themes.h index 472b6b1..35f53b1 100644 --- a/src/themes.h +++ b/src/themes.h @@ -44,6 +44,7 @@ #define MINI_CASE_SENSITIVE #include "../include/ini.h" #include "utils.h" +#include "../include/export_definitions.h" namespace Themes { @@ -118,7 +119,7 @@ namespace Themes { ~SlateTheme() = default; }; - class IniOptions { + class EXPORT IniOptions { public: IniOptions(); ~IniOptions() {}; From 3c20f2103db09b3659fb677fd6ca39b77ecc6c7e Mon Sep 17 00:00:00 2001 From: kcleal Date: Mon, 26 Feb 2024 12:51:32 +0000 Subject: [PATCH 02/51] updates for gwplot --- src/drawing.cpp | 7 ---- src/hts_funcs.cpp | 1 - src/hts_funcs.h | 5 ++- src/main.cpp | 41 +++++++++--------- src/menu.cpp | 2 +- src/menu.h | 2 +- src/plot_manager.cpp | 98 ++++++++++++++++++++++++++------------------ src/plot_manager.h | 24 +++++++---- src/themes.cpp | 71 ++++++++++++++++++++++++++++---- src/themes.h | 16 +++++++- src/utils.h | 2 + 11 files changed, 180 insertions(+), 89 deletions(-) diff --git a/src/drawing.cpp b/src/drawing.cpp index 1e39ecd..0903a2a 100644 --- a/src/drawing.cpp +++ b/src/drawing.cpp @@ -687,7 +687,6 @@ namespace Drawing { SkRect rect; SkPath path; - const Themes::BaseTheme &theme = opts.theme; std::vector text_ins, text_del; @@ -711,16 +710,12 @@ namespace Drawing { float pH_05 = pH * 0.05; float pH_95 = pH * 0.95; - for (const auto &a: cl.readQueue) { - int Y = a.y; if (Y < 0) { continue; } - bool indelTextFits = fonts.overlayHeight * 0.7 < yScaling; - int mapq = a.delegate->core.qual; float yScaledOffset = (Y * yScaling) + yOffset; chooseFacecolors(mapq, a, faceColor, theme); @@ -741,7 +736,6 @@ namespace Drawing { int lastEnd = 1215752191; int starti; bool line_only; - for (size_t idx = 0; idx < nBlocks; ++idx) { starti = (int) a.block_starts[idx]; if (idx > 0) { @@ -780,7 +774,6 @@ namespace Drawing { textDrop, text_del, indelTextFits); } } - // add soft-clip blocks int start = (int) a.pos - regionBegin; int end = (int) a.reference_end - regionBegin; diff --git a/src/hts_funcs.cpp b/src/hts_funcs.cpp index 653920a..502d219 100644 --- a/src/hts_funcs.cpp +++ b/src/hts_funcs.cpp @@ -433,7 +433,6 @@ namespace HGW { // if (region->end == 0) { // return; // } - bam1_t *src; hts_itr_t *iter_q; int tid = sam_hdr_name2tid(hdr_ptr, region->chrom.c_str()); diff --git a/src/hts_funcs.h b/src/hts_funcs.h index 9e270ff..b0cf3c6 100644 --- a/src/hts_funcs.h +++ b/src/hts_funcs.h @@ -17,8 +17,9 @@ #include "parser.h" #include "../include/IITree.h" #include "../include/unordered_dense.h" -#include "../lib/libBigWig/bigWig.h" -//#include "../include/strnatcmp.h" +#include "bigWig.h" +//#include "../lib/libBigWig/bigWig.h" + #include "../include/glob_cpp.hpp" #include "segments.h" #include "themes.h" diff --git a/src/main.cpp b/src/main.cpp index 9557a7f..3bc8535 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -413,7 +413,7 @@ int main(int argc, char *argv[]) { iopts.link_op = 2; } else { std::cerr << "Link type not known [none/sv/all]\n"; - std::exit(-1); + std::exit(-1);std::cerr << " 52 \n"; } } @@ -669,7 +669,7 @@ int main(int argc, char *argv[]) { SkCanvas *pageCanvas = pdfDocument->beginPage(iopts.dimensions.x, iopts.dimensions.y); plotter.fb_width = iopts.dimensions.x; plotter.fb_height = iopts.dimensions.y; - plotter.runDraw(pageCanvas); + plotter.runDrawOnCanvas(pageCanvas); pdfDocument->close(); buffer.writeToStream(&out); } else { @@ -677,7 +677,7 @@ int main(int argc, char *argv[]) { plotter.fb_height = iopts.dimensions.y; SkPictureRecorder recorder; SkCanvas* canvas = recorder.beginRecording(SkRect::MakeWH(iopts.dimensions.x, iopts.dimensions.y)); - plotter.runDraw(canvas); + plotter.runDrawOnCanvas(canvas); sk_sp picture = recorder.finishRecordingAsPicture(); std::unique_ptr svgCanvas = SkSVGCanvas::Make(SkRect::MakeWH(iopts.dimensions.x, iopts.dimensions.y), &out); if (svgCanvas) { @@ -693,15 +693,16 @@ int main(int argc, char *argv[]) { if (!regions.empty()) { // plot target regions plotter.setRasterSize(iopts.dimensions.x, iopts.dimensions.y); plotter.gap = 0; - sk_sp rasterSurface = SkSurface::MakeRasterN32Premul(iopts.dimensions.x, - iopts.dimensions.y); - SkCanvas *canvas = rasterSurface->getCanvas(); + plotter.makeRasterSurface(); +// sk_sp rasterSurface = SkSurface::MakeRasterN32Premul(iopts.dimensions.x, +// iopts.dimensions.y); +// SkCanvas *canvas = rasterSurface->getCanvas(); if (iopts.link_op == 0) { - plotter.runDrawNoBuffer(canvas); + plotter.runDrawNoBuffer(); } else { - plotter.runDraw(canvas); + plotter.runDraw(); } - img = rasterSurface->makeImageSnapshot(); + img = plotter.rasterSurface->makeImageSnapshot(); if (outdir.empty()) { std::string fpath; @@ -773,10 +774,11 @@ int main(int argc, char *argv[]) { block += 1; mtx.unlock(); Manager::GwPlot *plt = managers[this_block]; + plt->makeRasterSurface(); std::vector &all_regions = jobs[this_block]; - sk_sp rasterSurface = SkSurface::MakeRasterN32Premul( - iopts.dimensions.x, iopts.dimensions.y); - SkCanvas *canvas = rasterSurface->getCanvas(); +// sk_sp rasterSurface = SkSurface::MakeRasterN32Premul( +// iopts.dimensions.x, iopts.dimensions.y); +// SkCanvas *canvas = rasterSurface->getCanvas(); for (auto &rgn: all_regions) { plt->collections.clear(); delete plt->regions[0].refSeq; @@ -784,11 +786,11 @@ int main(int argc, char *argv[]) { plt->regions[0].start = rgn.start; plt->regions[0].end = rgn.end; if (iopts.link_op == 0) { - plt->runDrawNoBuffer(canvas); + plt->runDrawNoBuffer(); } else { - plt->runDraw(canvas); + plt->runDraw(); } - sk_sp img(rasterSurface->makeImageSnapshot()); + sk_sp img(plt->rasterSurface->makeImageSnapshot()); std::filesystem::path fname = "GW~" + plt->regions[0].chrom + "~" + std::to_string(plt->regions[0].start) + "~" + std::to_string(plt->regions[0].end) + "~.png"; @@ -901,18 +903,19 @@ int main(int argc, char *argv[]) { block += 1; mtx.unlock(); Manager::GwPlot *plt = managers[this_block]; - sk_sp rasterSurface = SkSurface::MakeRasterN32Premul(iopts.dimensions.x, iopts.dimensions.y); - SkCanvas *canvas = rasterSurface->getCanvas(); + plt->makeRasterSurface(); +// sk_sp rasterSurface = SkSurface::MakeRasterN32Premul(iopts.dimensions.x, iopts.dimensions.y); +// SkCanvas *canvas = rasterSurface->getCanvas(); for (int i = a; i < b; ++i) { Manager::VariantJob job = jobs[i]; plt->setVariantSite(job.chrom, job.start, job.chrom2, job.stop); - plt->runDrawNoBuffer(canvas); + plt->runDrawNoBuffer(); // if (plt->opts.low_memory && plt->opts.link_op == 0) { // plt->runDrawNoBuffer(canvas); // } else { // plt->runDraw(canvas); // } - sk_sp img(rasterSurface->makeImageSnapshot()); + sk_sp img(plt->rasterSurface->makeImageSnapshot()); std::filesystem::path fname = job.varType + "~" + job.chrom + "~" + std::to_string(job.start) + "~" + job.chrom2 + "~" + std::to_string(job.stop) + "~" + job.rid + ".png"; std::filesystem::path full_path = outdir / fname; Manager::imageToPng(img, full_path); diff --git a/src/menu.cpp b/src/menu.cpp index 4dcd345..2619c1d 100644 --- a/src/menu.cpp +++ b/src/menu.cpp @@ -99,7 +99,7 @@ namespace Menu { else { return Themes::MenuTable::MAIN; } } - void drawMenu(SkCanvas *canvas, GrDirectContext *sContext, SkSurface *sSurface, Themes::IniOptions &opts, Themes::Fonts &fonts, float monitorScale, float fb_width, float fb_height, + void drawMenu(SkCanvas *canvas, Themes::IniOptions &opts, Themes::Fonts &fonts, float monitorScale, float fb_width, float fb_height, std::string inputText, int charIndex) { SkRect rect; SkPath path; diff --git a/src/menu.h b/src/menu.h index 98bac93..1dd7800 100644 --- a/src/menu.h +++ b/src/menu.h @@ -37,7 +37,7 @@ namespace Menu { - void drawMenu(SkCanvas *canvas, GrDirectContext *sContext, SkSurface *sSurface, Themes::IniOptions &opts, Themes::Fonts &fonts, float monitorScale, float fb_width, float fb_height, std::string inputText, int charIndex); + void drawMenu(SkCanvas *canvas, Themes::IniOptions &opts, Themes::Fonts &fonts, float monitorScale, float fb_width, float fb_height, std::string inputText, int charIndex); void menuMousePos(Themes::IniOptions &opts, Themes::Fonts &fonts, float xPos, float yPos, float fb_height, float fb_width, bool *redraw); diff --git a/src/plot_manager.cpp b/src/plot_manager.cpp index e63434f..f5979b6 100644 --- a/src/plot_manager.cpp +++ b/src/plot_manager.cpp @@ -63,7 +63,7 @@ namespace Manager { } - GwPlot::GwPlot(std::string reference, std::vector &bampaths, Themes::IniOptions &opt, std::vector ®ions, + 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; @@ -161,6 +161,16 @@ namespace Manager { std::cout << "GLFW Error: " << err_str << std::endl; } + int GwPlot::makeRasterSurface() { + 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); + return pixelMemory.size(); + } + void GwPlot::init(int width, int height) { glfwSetErrorCallback(ErrorCallback); @@ -244,12 +254,7 @@ namespace Manager { glfwMakeContextCurrent(window); setGlfwFrameBufferSize(); - SkImageInfo info = SkImageInfo::MakeN32Premul(opts.dimensions.x * monitorScale, opts.dimensions.y * monitorScale); - size_t rowBytes = info.minRowBytes(); - size_t size = info.computeByteSize(rowBytes); - pixelMemory.resize(size); - rasterSurface = SkSurface::MakeRasterDirect( - info, &pixelMemory[0], rowBytes); + makeRasterSurface(); } @@ -285,6 +290,17 @@ namespace Manager { } } + void GwPlot::addBam(std::string &bam_path) { + 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()); + indexes.push_back(idx); + } + void GwPlot::addVariantTrack(std::string &path, int startIndex, bool cacheStdin, bool useFullPath) { std::string variantFilename; if (!useFullPath) { @@ -445,7 +461,9 @@ namespace Manager { printIndexInfo(); } } - drawOverlay(sSurface->getCanvas(), sContext, sSurface); + drawOverlay(sSurface->getCanvas()); + sContext->flush(); + glfwSwapBuffers(window); if (resizeTriggered && std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - resizeTimer) > 100ms) { imageCache.clear(); @@ -769,35 +787,21 @@ namespace Manager { } void GwPlot::drawScreenNoBuffer(SkCanvas* canvas, GrDirectContext* sContext, SkSurface *sSurface) { - -// std::chrono::high_resolution_clock::time_point initial = std::chrono::high_resolution_clock::now(); - - SkCanvas* canvasR = rasterSurface->getCanvas(); - canvas->drawPaint(opts.theme.bgPaint); - canvasR->drawPaint(opts.theme.bgPaint); - frameId += 1; -// setGlfwFrameBufferSize(); if (regions.empty()) { setScaling(); } else { -// runDrawNoBuffer(canvas, sContext); - runDrawNoBuffer(canvasR); + runDrawNoBuffer(); } -// imageCacheQueue.emplace_back(frameId, sSurface->makeImageSnapshot()); imageCacheQueue.emplace_back(frameId, rasterSurface->makeImageSnapshot()); - canvas->drawImage(imageCacheQueue.back().second, 0, 0); - sContext->flush(); glfwSwapBuffers(window); redraw = false; - -// std::cerr << " time nobuffer " << (std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - initial).count()) << std::endl; } - void GwPlot::drawOverlay(SkCanvas *canvas, GrDirectContext *sContext, SkSurface *sSurface) { + void GwPlot::drawOverlay(SkCanvas *canvas) { if (!imageCacheQueue.empty()) { while (imageCacheQueue.front().first != frameId) { imageCacheQueue.pop_front(); @@ -814,7 +818,7 @@ namespace Manager { bg.setAlpha(220); canvas->drawPaint(bg); } - Menu::drawMenu(sSurface->getCanvas(), sContext, sSurface, opts, fonts, monitorScale, fb_width, fb_height, inputText, charIndex); + Menu::drawMenu(canvas, opts, fonts, monitorScale, fb_width, fb_height, inputText, charIndex); } else if (regionSelectionTriggered && regions.size() > 1) { SkRect rect{}; float step = (float)fb_width / (float)regions.size(); @@ -1026,8 +1030,6 @@ namespace Manager { tcMenu.setAntiAlias(true); canvas->drawTextBlob(blob.get(), txt_start, (float)fb_height / 2, tcMenu); } - sContext->flush(); - glfwSwapBuffers(window); } void GwPlot::tileDrawingThread(SkCanvas *canvas, GrDirectContext *sContext, SkSurface *sSurface) { @@ -1040,7 +1042,7 @@ namespace Manager { bool c = imageCache.contains(i); if (!c && i < (int)currentVarTrack->multiRegions.size() && !bams.empty()) { regions = currentVarTrack->multiRegions[i]; - runDraw(canvas); + runDraw(); sk_sp img(sSurface->makeImageSnapshot()); imageCache[i] = img; sContext->flush(); @@ -1162,7 +1164,7 @@ namespace Manager { redraw = false; } - void GwPlot::runDraw(SkCanvas *canvas) { + void GwPlot::runDrawOnCanvas(SkCanvas *canvas) { fetchRefSeqs(); processBam(); setScaling(); @@ -1178,14 +1180,20 @@ namespace Manager { Drawing::drawTracks(opts, fb_width, fb_height, canvas, totalTabixY, tabixY, tracks, regions, fonts, gap, monitorScale); } - void GwPlot::runDrawNoBuffer(SkCanvas *canvas) { + void GwPlot::runDraw() { + runDrawOnCanvas(rasterSurface->getCanvas()); + } + + void GwPlot::runDrawNoBuffer() { // std::chrono::high_resolution_clock::time_point initial = std::chrono::high_resolution_clock::now(); if (bams.empty()) { return; } -// std::chrono::high_resolution_clock::time_point initial = std::chrono::high_resolution_clock::now(); + SkCanvas* canvas = rasterSurface->getCanvas(); + canvas->drawPaint(opts.theme.bgPaint); + fetchRefSeqs(); // This is a subset of processBam function: @@ -1217,7 +1225,6 @@ namespace Manager { } setScaling(); canvas->drawPaint(opts.theme.bgPaint); - idx = 0; for (int i=0; i<(int)bams.size(); ++i) { htsFile* b = bams[i]; @@ -1235,6 +1242,7 @@ namespace Manager { idx += 1; } } + if (opts.max_coverage) { Drawing::drawCoverage(opts, collections, canvas, fonts, covY, refSpace); } @@ -1243,10 +1251,26 @@ namespace Manager { 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, collections, canvas, fai, headers, regions.size(), fb_width, fb_height, monitorScale); - // std::cerr << " time runDrawNoBuffer " << (std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - initial).count()) << std::endl; } + sk_sp GwPlot::makeImage() { + makeRasterSurface(); + runDraw(); + sk_sp img(rasterSurface->makeImageSnapshot()); + return img; + } + + void GwPlot::rasterToPng(const char* path) { + sk_sp img(rasterSurface->makeImageSnapshot()); + if (!img) { return; } + sk_sp png(img->encodeToData()); + if (!png) { return; } + FILE* fout = fopen(path, "w"); + fwrite(png->data(), 1, png->size(), fout); + fclose(fout); + } + void imageToPng(sk_sp &img, std::filesystem::path &path) { if (!img) { return; } sk_sp png(img->encodeToData()); @@ -1290,13 +1314,5 @@ namespace Manager { fclose(fout); } - sk_sp GwPlot::makeImage() { - setScaling(); - sk_sp rasterSurface = SkSurface::MakeRasterN32Premul(fb_width, fb_height); - SkCanvas *canvas = rasterSurface->getCanvas(); - runDraw(canvas); - sk_sp img(rasterSurface->makeImageSnapshot()); - return img; - } } diff --git a/src/plot_manager.h b/src/plot_manager.h index 45147a4..61aac55 100644 --- a/src/plot_manager.h +++ b/src/plot_manager.h @@ -35,6 +35,7 @@ #include "utils.h" #include "segments.h" #include "themes.h" +#include "export_definitions.h" #define SK_GL #include "include/gpu/GrBackendSurface.h" @@ -69,7 +70,7 @@ namespace Manager { /* * Deals with managing all data and plotting */ - class GwPlot { + class EXPORT GwPlot { public: GwPlot(std::string reference, std::vector &bampaths, Themes::IniOptions &opts, std::vector ®ions, std::vector &track_paths); @@ -81,6 +82,8 @@ namespace Manager { int regionSelection, variantFileSelection; bool drawToBackWindow; + std::vector pixelMemory; + std::string reference; std::vector bam_paths; @@ -131,6 +134,12 @@ namespace Manager { void setRasterSize(int width, int height); + int makeRasterSurface(); + + void rasterToPng(const char* path); + + void addBam(std::string &bam_path); + void addVariantTrack(std::string &path, int startIndex, bool cacheStdin, bool useFullPath); void addFilter(std::string &filter_str); @@ -153,8 +162,6 @@ namespace Manager { void setVariantSite(std::string &chrom, long start, std::string &chrom2, long stop); -// void appendVariantSite(std::string &chrom, long start, std::string &chrom2, long stop, std::string & rid, std::string &label, std::string &vartype); - int startUI(GrDirectContext* sContext, SkSurface *sSurface, int delay); void keyPress(GLFWwindow* window, int key, int scancode, int action, int mods); @@ -169,9 +176,11 @@ namespace Manager { void pathDrop(GLFWwindow* window, int count, const char** paths); - void runDraw(SkCanvas *canvas); + void runDraw(); + + void runDrawOnCanvas(SkCanvas *canvas); - void runDrawNoBuffer(SkCanvas *canvas); + void runDrawNoBuffer(); sk_sp makeImage(); @@ -211,8 +220,6 @@ namespace Manager { float yScaling; - std::vector pixelMemory; - // std::vector> extraPixelArrays; // one for each thread GLFWcursor* vCursor; @@ -232,7 +239,7 @@ namespace Manager { void drawScreenNoBuffer(SkCanvas* canvas, GrDirectContext* sContext, SkSurface *sSurface); - void drawOverlay(SkCanvas* canvas, GrDirectContext* sContext, SkSurface *sSurface); + void drawOverlay(SkCanvas* canvas); void tileDrawingThread(SkCanvas* canvas, GrDirectContext* sContext, SkSurface *sSurface); @@ -251,7 +258,6 @@ namespace Manager { void highlightQname(); void updateCursorGenomePos(float xOffset, float xScaling, float xPos, Utils::Region *region, int bamIdx); - //void updateCursorGenomePos(Segs::ReadCollection &cl, float xPos); void updateSlider(float xPos); diff --git a/src/themes.cpp b/src/themes.cpp index 5d231e0..91c09d2 100644 --- a/src/themes.cpp +++ b/src/themes.cpp @@ -11,7 +11,7 @@ namespace Themes { - BaseTheme::BaseTheme() { + EXPORT BaseTheme::BaseTheme() { fcCoverage.setStyle(SkPaint::kStrokeAndFill_Style); fcCoverage.setStrokeWidth(0); @@ -187,6 +187,51 @@ namespace Themes { } } + void BaseTheme::setPaintARGB(int paint_enum, int a, int r, int g, int b) { + switch (paint_enum) { + case GwPaint::bgPaint: this->bgPaint.setARGB(a, r, g, b); break; + case GwPaint::fcNormal: this->fcNormal.setARGB(a, r, g, b); break; + case GwPaint::fcDel: this->fcDel.setARGB(a, r, g, b); break; + case GwPaint::fcDup: this->fcDup.setARGB(a, r, g, b); break; + case GwPaint::fcInvF: this->fcInvF.setARGB(a, r, g, b); break; + case GwPaint::fcInvR: this->fcInvR.setARGB(a, r, g, b); break; + case GwPaint::fcTra: this->fcTra.setARGB(a, r, g, b); break; + case GwPaint::fcIns: this->fcIns.setARGB(a, r, g, b); break; + case GwPaint::fcSoftClip: this->fcSoftClip.setARGB(a, r, g, b); break; + case GwPaint::fcA: this->fcA.setARGB(a, r, g, b); break; + case GwPaint::fcT: this->fcT.setARGB(a, r, g, b); break; + case GwPaint::fcC: this->fcC.setARGB(a, r, g, b); break; + case GwPaint::fcG: this->fcG.setARGB(a, r, g, b); break; + case GwPaint::fcN: this->fcN.setARGB(a, r, g, b); break; + case GwPaint::fcCoverage: this->fcCoverage.setARGB(a, r, g, b); break; + case GwPaint::fcTrack: this->fcTrack.setARGB(a, r, g, b); break; + case GwPaint::fcNormal0: this->fcNormal0.setARGB(a, r, g, b); break; + case GwPaint::fcDel0: this->fcDel0.setARGB(a, r, g, b); break; + case GwPaint::fcDup0: this->fcDup0.setARGB(a, r, g, b); break; + case GwPaint::fcInvF0: this->fcInvF0.setARGB(a, r, g, b); break; + case GwPaint::fcInvR0: this->fcInvR0.setARGB(a, r, g, b); break; + case GwPaint::fcTra0: this->fcTra0.setARGB(a, r, g, b); break; + case GwPaint::fcSoftClip0: this->fcSoftClip0.setARGB(a, r, g, b); break; + case GwPaint::fcBigWig: this->fcBigWig.setARGB(a, r, g, b); break; + case GwPaint::ecMateUnmapped: this->ecMateUnmapped.setARGB(a, r, g, b); break; + case GwPaint::ecSplit: this->ecSplit.setARGB(a, r, g, b); break; + case GwPaint::ecSelected: this->ecSelected.setARGB(a, r, g, b); break; + case GwPaint::lcJoins: this->lcJoins.setARGB(a, r, g, b); break; + case GwPaint::lcCoverage: this->lcCoverage.setARGB(a, r, g, b); break; + case GwPaint::lcLightJoins: this->lcLightJoins.setARGB(a, r, g, b); break; + case GwPaint::insF: this->insF.setARGB(a, r, g, b); break; + case GwPaint::insS: this->insS.setARGB(a, r, g, b); break; + case GwPaint::lcLabel: this->lcLabel.setARGB(a, r, g, b); break; + case GwPaint::lcBright: this->lcBright.setARGB(a, r, g, b); break; + case GwPaint::tcDel: this->tcDel.setARGB(a, r, g, b); break; + case GwPaint::tcIns: this->tcIns.setARGB(a, r, g, b); break; + case GwPaint::tcLabels: this->tcLabels.setARGB(a, r, g, b); break; + case GwPaint::tcBackground: this->tcBackground.setARGB(a, r, g, b); break; + case GwPaint::marker_paint: this->marker_paint.setARGB(a, r, g, b); break; + default: break; + } + } + IgvTheme::IgvTheme() { name = "igv"; fcCoverage.setARGB(255, 195, 195, 195); @@ -354,6 +399,18 @@ namespace Themes { repeat_command=GLFW_KEY_R; } + void IniOptions::setTheme(std::string &theme_str) { + if (theme_str == "slate") { + this->theme_str = theme_str; theme = SlateTheme(); + } else if (theme_str == "igv") { + this->theme_str = theme_str; theme = IgvTheme(); + } else if (theme_str == "dark") { + this->theme_str = theme_str; theme = DarkTheme(); + } else { + std::cerr << "theme_str must be slate, igv, or dark\n"; + } + } + void IniOptions::getOptionsFromIni() { ankerl::unordered_dense::map key_table; @@ -481,12 +538,12 @@ namespace Themes { bool IniOptions::readIni() { -# if defined(_WIN32) || defined(_WIN64) - const char *homedrive_c = std::getenv("HOMEDRIVE"); - const char *homepath_c = std::getenv("HOMEPATH"); - std::string homedrive(homedrive_c ? homedrive_c : ""); - std::string homepath(homepath_c ? homepath_c : ""); - std::string home = homedrive + homepath; +#if defined(_WIN32) || defined(_WIN64) + const char *homedrive_c = std::getenv("HOMEDRIVE"); + const char *homepath_c = std::getenv("HOMEPATH"); + std::string homedrive(homedrive_c ? homedrive_c : ""); + std::string homepath(homepath_c ? homepath_c : ""); + std::string home = homedrive + homepath; #else struct passwd *pw = getpwuid(getuid()); std::string home(pw->pw_dir); diff --git a/src/themes.h b/src/themes.h index 35f53b1..50bae5e 100644 --- a/src/themes.h +++ b/src/themes.h @@ -64,7 +64,19 @@ namespace Themes { constexpr float base_qual_alpha[11] = {51, 51, 51, 51, 51, 128, 128, 128, 128, 128, 255}; - class BaseTheme { + /* + bg - background, fc - face color, ec - edge color, lc - line color, tc - text color + if an item has 0 at the end this is the color when mapq == 0 + */ + enum GwPaint { + bgPaint, fcNormal, fcDel, fcDup, fcInvF, fcInvR, fcTra, fcIns, fcSoftClip, + fcA, fcT, fcC, fcG, fcN, fcCoverage, fcTrack, fcNormal0, fcDel0, fcDup0, fcInvF0, fcInvR0, fcTra0, + fcSoftClip0, fcBigWig, mate_fc, mate_fc0, ecMateUnmapped, ecSplit, ecSelected, + lcJoins, lcCoverage, lcLightJoins, insF, insS, lcLabel, lcBright, tcDel, tcIns, tcLabels, tcBackground, + marker_paint + }; + + class EXPORT BaseTheme { public: BaseTheme(); ~BaseTheme() = default; @@ -98,6 +110,7 @@ namespace Themes { std::array, 16> BasePaints; void setAlphas(); + void setPaintARGB(int paint_enum, int alpha, int red, int green, int blue); }; @@ -159,6 +172,7 @@ namespace Themes { bool readIni(); static std::filesystem::path writeDefaultIni(std::filesystem::path &homedir, std::filesystem::path &home_config, std::filesystem::path &gwIni); void getOptionsFromIni(); + void setTheme(std::string &theme_str); }; diff --git a/src/utils.h b/src/utils.h index 53c62bf..d2f8d7b 100644 --- a/src/utils.h +++ b/src/utils.h @@ -78,6 +78,8 @@ namespace Utils { chrom = ""; start = -1; end = -1; + markerPos = -1; + markerPosEnd = -1; refSeq = nullptr; } }; From 80632e558f95b8c3ae47542aa9e56f700b3a43d9 Mon Sep 17 00:00:00 2001 From: kcleal Date: Tue, 27 Feb 2024 09:46:28 +0000 Subject: [PATCH 03/51] checked out to feature --- .gitignore | 1 + Makefile | 4 ++-- src/plot_manager.cpp | 4 ++-- src/plot_manager.h | 5 +++-- src/segments.cpp | 2 -- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 0d3dc91..27cd7b4 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,4 @@ /lib/libBigWig/bwWrite.o /lib/libBigWig/io.o /libgw/ +libgw.so diff --git a/Makefile b/Makefile index f9fc56c..b61f799 100644 --- a/Makefile +++ b/Makefile @@ -135,10 +135,10 @@ $(TARGET): $(OBJECTS) clean: -rm -f *.o ./src/*.o ./src/*.o.tmp ./lib/libBigWig/*.o -rm -f $(TARGET) - -rm -rf libgw + -rm -rf libgw* -SHARED_TARGET = libgw/libgw.so +SHARED_TARGET = libgw.so ifeq ($(PLATFORM),"Darwin") SHARED_TARGET = libgw/libgw.dylib endif diff --git a/src/plot_manager.cpp b/src/plot_manager.cpp index f5979b6..963711a 100644 --- a/src/plot_manager.cpp +++ b/src/plot_manager.cpp @@ -1204,6 +1204,7 @@ namespace Manager { for (int j=0; j<(int)regions.size(); ++j) { Utils::Region *reg = ®ions[j]; Segs::ReadCollection &col = collections[idx]; + col.skipDrawingCoverage = false; col.bamIdx = i; if (!col.levelsStart.empty()) { col.clear(); @@ -1224,7 +1225,7 @@ namespace Manager { } } setScaling(); - canvas->drawPaint(opts.theme.bgPaint); +// canvas->drawPaint(opts.theme.bgPaint); idx = 0; for (int i=0; i<(int)bams.size(); ++i) { htsFile* b = bams[i]; @@ -1242,7 +1243,6 @@ namespace Manager { idx += 1; } } - if (opts.max_coverage) { Drawing::drawCoverage(opts, collections, canvas, fonts, covY, refSpace); } diff --git a/src/plot_manager.h b/src/plot_manager.h index 61aac55..be90f0a 100644 --- a/src/plot_manager.h +++ b/src/plot_manager.h @@ -82,6 +82,9 @@ namespace Manager { int regionSelection, variantFileSelection; bool drawToBackWindow; + bool redraw; + bool processed; + std::vector pixelMemory; std::string reference; @@ -191,8 +194,6 @@ namespace Manager { private: long frameId; - bool redraw; - bool processed; bool drawLine; bool resizeTriggered; bool regionSelectionTriggered; diff --git a/src/segments.cpp b/src/segments.cpp index 16cd662..aee5ce9 100644 --- a/src/segments.cpp +++ b/src/segments.cpp @@ -414,8 +414,6 @@ namespace Segs { if (block_s >= end) { break; } uint32_t block_e = align.block_ends[idx]; if (block_e < begin) { continue; } -// uint32_t s = (block_s >= begin) ? block_s - begin : 0; -// uint32_t e = (block_e < end) ? block_e - begin : l_arr; uint32_t s = std::max(block_s, begin) - begin; uint32_t e = std::min(block_e, end) - begin; arr[s] += 1; From 60567ad6978dbfecebec3c7af4b5a204a872d56f Mon Sep 17 00:00:00 2001 From: kcleal Date: Tue, 27 Feb 2024 15:09:26 +0000 Subject: [PATCH 04/51] updated Makefile --- Makefile | 3 --- 1 file changed, 3 deletions(-) diff --git a/Makefile b/Makefile index b61f799..ebe3196 100644 --- a/Makefile +++ b/Makefile @@ -139,9 +139,6 @@ clean: SHARED_TARGET = libgw.so -ifeq ($(PLATFORM),"Darwin") - SHARED_TARGET = libgw/libgw.dylib -endif shared: CXXFLAGS += -fPIC shared: $(OBJECTS) From 50fe008aab1e2499e249c40b4dc899d4f6c2d505 Mon Sep 17 00:00:00 2001 From: kcleal Date: Tue, 27 Feb 2024 16:28:07 +0000 Subject: [PATCH 05/51] updated EXPORT --- include/export_definitions.h | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/include/export_definitions.h b/include/export_definitions.h index 4478ad6..4b55fee 100644 --- a/include/export_definitions.h +++ b/include/export_definitions.h @@ -4,14 +4,8 @@ #pragma once -#if defined _WIN32 || defined __CYGWIN__ - #ifdef BUILDING_LIBGW - #define EXPORT __declspec(dllexport) - #else - #define EXPORT __declspec(dllimport) - #endif -#else - #if __GNUC__ >= 4 +#if defined(__GNUC__) && __GNUC__ >= 4 + #if BUILDING_LIBGW #define EXPORT __attribute__ ((visibility ("default"))) #else #define EXPORT From 824be151b066f2bf276f30eeaa299069d604eec5 Mon Sep 17 00:00:00 2001 From: kcleal Date: Tue, 27 Feb 2024 22:20:42 +0000 Subject: [PATCH 06/51] Update Makefile --- Makefile | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index ebe3196..c8ffe9c 100644 --- a/Makefile +++ b/Makefile @@ -138,11 +138,23 @@ clean: -rm -rf libgw* -SHARED_TARGET = libgw.so +UNAME_S := $(shell uname -s) + + +ifeq ($(UNAME_S),Linux) + SHARED_TARGET = libgw.so +endif +ifeq ($(UNAME_S),Darwin) + SHARED_TARGET = libgw.dylib +endif shared: CXXFLAGS += -fPIC shared: $(OBJECTS) -mkdir -p libgw/include -cp src/*.h libgw/include -cp include/*.h* libgw/include +ifeq ($(UNAME_S),Darwin) + $(CXX) $(OBJECTS) $(LDFLAGS) $(LDLIBS) -dynamiclib -DBUILDING_LIBGW -o $(SHARED_TARGET) +else $(CXX) $(OBJECTS) $(LDFLAGS) $(LDLIBS) -shared -DBUILDING_LIBGW -o $(SHARED_TARGET) +endif From ed8e9dfa8b7e84c78d54c9198d9ae18b063aaf79 Mon Sep 17 00:00:00 2001 From: kcleal Date: Wed, 28 Feb 2024 12:11:57 +0000 Subject: [PATCH 07/51] Updated EXPORT --- Makefile | 7 +++---- include/export_definitions.h | 3 +++ src/themes.cpp | 6 +++--- src/themes.h | 8 ++++---- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/Makefile b/Makefile index c8ffe9c..0e0b71d 100644 --- a/Makefile +++ b/Makefile @@ -9,10 +9,10 @@ debug: default # set system PLATFORM= +UNAME_S := $(shell uname -s) ifeq ($(OS),Windows_NT) # assume we are using msys2-ucrt64 env PLATFORM = "Windows" else - UNAME_S := $(shell uname -s) ifeq ($(UNAME_S),Linux) PLATFORM = "Linux" else ifeq ($(UNAME_S),Darwin) @@ -138,8 +138,6 @@ clean: -rm -rf libgw* -UNAME_S := $(shell uname -s) - ifeq ($(UNAME_S),Linux) SHARED_TARGET = libgw.so @@ -148,11 +146,12 @@ ifeq ($(UNAME_S),Darwin) SHARED_TARGET = libgw.dylib endif -shared: CXXFLAGS += -fPIC +shared: CXXFLAGS += -fPIC -DBUILDING_LIBGW shared: $(OBJECTS) -mkdir -p libgw/include -cp src/*.h libgw/include -cp include/*.h* libgw/include + ifeq ($(UNAME_S),Darwin) $(CXX) $(OBJECTS) $(LDFLAGS) $(LDLIBS) -dynamiclib -DBUILDING_LIBGW -o $(SHARED_TARGET) else diff --git a/include/export_definitions.h b/include/export_definitions.h index 4b55fee..4a83f5a 100644 --- a/include/export_definitions.h +++ b/include/export_definitions.h @@ -10,4 +10,7 @@ #else #define EXPORT #endif +#else + #error "__GNUC__ not defined or version is less than 4" #endif + diff --git a/src/themes.cpp b/src/themes.cpp index 91c09d2..4a4df53 100644 --- a/src/themes.cpp +++ b/src/themes.cpp @@ -5,8 +5,8 @@ #include "menu.h" #include "themes.h" #include "glfw_keys.h" -#include "../include/defaultIni.hpp" -#include "../include/unordered_dense.h" +#include "defaultIni.hpp" +#include "unordered_dense.h" namespace Themes { @@ -340,7 +340,7 @@ namespace Themes { ecSplit.setStrokeWidth(1); } - IniOptions::IniOptions() { + EXPORT IniOptions::IniOptions() { menu_level = ""; menu_table = MAIN; theme_str = "dark"; diff --git a/src/themes.h b/src/themes.h index 50bae5e..59aff00 100644 --- a/src/themes.h +++ b/src/themes.h @@ -39,12 +39,12 @@ #include "include/core/SkFont.h" #include "include/core/SkTextBlob.h" -#include "../include/argparse.h" -#include "../include/glob_cpp.hpp" +#include "argparse.h" +#include "glob_cpp.hpp" #define MINI_CASE_SENSITIVE -#include "../include/ini.h" +#include "ini.h" #include "utils.h" -#include "../include/export_definitions.h" +#include "export_definitions.h" namespace Themes { From c3d2bed59a06a3016236aeb6313b6e333a7b77c8 Mon Sep 17 00:00:00 2001 From: kcleal Date: Thu, 29 Feb 2024 17:21:43 +0000 Subject: [PATCH 08/51] Added shared library build option --- .gitignore | 1 + Makefile | 15 +++++++++------ libgw/Makefile | 24 ++++++++++++++++++++++++ libgw/libgw.hpp | 31 +++++++++++++++++++++++++++++++ libgw/test.cpp | 13 +++++++++++++ src/themes.cpp | 4 +++- src/themes.h | 4 +++- 7 files changed, 84 insertions(+), 8 deletions(-) create mode 100644 libgw/Makefile create mode 100644 libgw/libgw.hpp create mode 100644 libgw/test.cpp diff --git a/.gitignore b/.gitignore index 27cd7b4..9877ece 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,4 @@ /lib/libBigWig/io.o /libgw/ libgw.so +/libgw.dylib diff --git a/Makefile b/Makefile index 0e0b71d..532d499 100644 --- a/Makefile +++ b/Makefile @@ -75,12 +75,12 @@ prep: CXXFLAGS += -Wall -std=c++17 -fno-common -fwrapv -fno-omit-frame-pointer -O3 -DNDEBUG -CPPFLAGS += -I./lib/libBigWig -I./include -I./src -I. +LIBGW_INCLUDE= +shared: LIBGW_INCLUDE=-I./libgw +CPPFLAGS += -I./lib/libBigWig -I./include -I. $(LIBGW_INCLUDE) -I./src LDLIBS += -lskia -lm -ljpeg -lpng -lsvg -lhts -lfontconfig -lpthread - - # set platform flags and libs ifeq ($(PLATFORM),"Linux") ifeq (${XDG_SESSION_TYPE},"wayland") # wayland is untested! @@ -104,13 +104,13 @@ ifeq ($(PLATFORM),"Linux") else ifeq ($(PLATFORM),"Darwin") CPPFLAGS += -I/usr/local/include - CXXFLAGS += -D OSX -stdlib=libc++ -arch x86_64 -fvisibility=hidden -mmacosx-version-min=10.15 -Wno-deprecated-declarations + CXXFLAGS += -D OSX -stdlib=libc++ -arch x86_64 -fvisibility=hidden -mmacosx-version-min=11 -Wno-deprecated-declarations LDFLAGS += -undefined dynamic_lookup -framework OpenGL -framework AppKit -framework ApplicationServices -mmacosx-version-min=10.15 -L/usr/local/lib LDLIBS += -lglfw -lzlib -lcurl -licu -ldl else ifeq ($(PLATFORM),"Arm64") CPPFLAGS += -I/usr/local/include - CXXFLAGS += -D OSX -stdlib=libc++ -arch arm64 -fvisibility=hidden -mmacosx-version-min=10.15 -Wno-deprecated-declarations + CXXFLAGS += -D OSX -stdlib=libc++ -arch arm64 -fvisibility=hidden -mmacosx-version-min=11 -Wno-deprecated-declarations LDFLAGS += -undefined dynamic_lookup -framework OpenGL -framework AppKit -framework ApplicationServices -mmacosx-version-min=10.15 -L/usr/local/lib LDLIBS += -lglfw -lzlib -lcurl -licu -ldl @@ -135,7 +135,7 @@ $(TARGET): $(OBJECTS) clean: -rm -f *.o ./src/*.o ./src/*.o.tmp ./lib/libBigWig/*.o -rm -f $(TARGET) - -rm -rf libgw* + -rm -rf libgw.* @@ -147,10 +147,13 @@ ifeq ($(UNAME_S),Darwin) endif shared: CXXFLAGS += -fPIC -DBUILDING_LIBGW + shared: $(OBJECTS) -mkdir -p libgw/include -cp src/*.h libgw/include -cp include/*.h* libgw/include + -cp lib/libBigWig/*.h libgw/include + -cp -rf lib/skia/include libgw/include ifeq ($(UNAME_S),Darwin) $(CXX) $(OBJECTS) $(LDFLAGS) $(LDLIBS) -dynamiclib -DBUILDING_LIBGW -o $(SHARED_TARGET) diff --git a/libgw/Makefile b/libgw/Makefile new file mode 100644 index 0000000..08e6f9a --- /dev/null +++ b/libgw/Makefile @@ -0,0 +1,24 @@ + +CXXFLAGS += -std=c++17 -DOSX -stdlib=libc++ -arch arm64 -mmacosx-version-min=10.15 +CPPFLAGS += -I./include +LDFLAGS += -L../ -undefined dynamic_lookup -framework OpenGL -framework AppKit -framework ApplicationServices +LDLIBS += -lgw +CXX=clang++ + + +TARGET: hello + +.PHONY: default clean + +default: $(TARGET) + +all: default + +SOURCES=$(wildcard *.cpp) + +hello: $(OBJECTS) + $(CXX) $(CXXFLAGS) $(CPPFLAGS) -g $(SOURCES) $(LDFLAGS) $(LDLIBS) -o $@ + +clean: + -rm -rf *.o ./*.o ./*.dSYM + -rm -f hello \ No newline at end of file diff --git a/libgw/libgw.hpp b/libgw/libgw.hpp new file mode 100644 index 0000000..f4a1ab2 --- /dev/null +++ b/libgw/libgw.hpp @@ -0,0 +1,31 @@ +// +// Created by Kez Cleal on 28/02/2024. +// + +#pragma once + +#include "themes.h" +#include "export_definitions.h" + + +namespace Gw { + + enum GwPaint { + bgPaint, fcNormal, fcDel, fcDup, fcInvF, fcInvR, fcTra, fcIns, fcSoftClip, + fcA, fcT, fcC, fcG, fcN, fcCoverage, fcTrack, fcNormal0, fcDel0, fcDup0, fcInvF0, fcInvR0, fcTra0, + fcSoftClip0, fcBigWig, mate_fc, mate_fc0, ecMateUnmapped, ecSplit, ecSelected, + lcJoins, lcCoverage, lcLightJoins, insF, insS, lcLabel, lcBright, tcDel, tcIns, tcLabels, tcBackground, + marker_paint + }; + + class EXPORT BaseTheme { + public: + BaseTheme(); + ~BaseTheme() = default; + + void setAlphas(); + void setPaintARGB(int paint_enum, int alpha, int red, int green, int blue); + + }; +} + diff --git a/libgw/test.cpp b/libgw/test.cpp new file mode 100644 index 0000000..743ac29 --- /dev/null +++ b/libgw/test.cpp @@ -0,0 +1,13 @@ +// +// Created by Kez Cleal on 29/02/2024. +// +#include +#include "libgw.hpp" + +int main(int argc, char *argv[]) { + Gw::BaseTheme t; + + int p = Gw::GwPaint::fcNormal; + t.setPaintARGB(p, 100, 100, 100, 100); + return 0; +} \ No newline at end of file diff --git a/src/themes.cpp b/src/themes.cpp index 4a4df53..1ed8d75 100644 --- a/src/themes.cpp +++ b/src/themes.cpp @@ -12,6 +12,7 @@ namespace Themes { EXPORT BaseTheme::BaseTheme() { +// BaseTheme::BaseTheme() { fcCoverage.setStyle(SkPaint::kStrokeAndFill_Style); fcCoverage.setStrokeWidth(0); @@ -340,7 +341,8 @@ namespace Themes { ecSplit.setStrokeWidth(1); } - EXPORT IniOptions::IniOptions() { +// EXPORT IniOptions::IniOptions() { + IniOptions::IniOptions() { menu_level = ""; menu_table = MAIN; theme_str = "dark"; diff --git a/src/themes.h b/src/themes.h index 59aff00..81a70b2 100644 --- a/src/themes.h +++ b/src/themes.h @@ -77,6 +77,7 @@ namespace Themes { }; class EXPORT BaseTheme { +// class BaseTheme { public: BaseTheme(); ~BaseTheme() = default; @@ -132,7 +133,8 @@ namespace Themes { ~SlateTheme() = default; }; - class EXPORT IniOptions { +// class EXPORT IniOptions { + class IniOptions { public: IniOptions(); ~IniOptions() {}; From ecf6892ebad31de48a608c3059c16b262c02c2a7 Mon Sep 17 00:00:00 2001 From: kcleal Date: Fri, 1 Mar 2024 12:47:41 +0000 Subject: [PATCH 09/51] Updated shared library --- Makefile | 19 ++++++------ ...dered_dense.h => ankerl_unordered_dense.h} | 0 libgw/libgw.hpp | 30 +++---------------- libgw/{ => test}/Makefile | 4 +-- libgw/{ => test}/test.cpp | 6 ++-- src/drawing.cpp | 4 +-- src/drawing.h | 4 +-- src/glfw_keys.cpp | 2 +- src/glfw_keys.h | 2 +- src/hts_funcs.cpp | 10 +++---- src/hts_funcs.h | 6 ++-- src/main.cpp | 4 +-- src/menu.cpp | 6 ++-- src/menu.h | 2 +- src/parser.cpp | 4 +-- src/parser.h | 2 +- src/plot_controls.cpp | 2 +- src/plot_manager.cpp | 6 ++-- src/plot_manager.h | 11 +++---- src/segments.cpp | 2 +- src/segments.h | 15 +++++----- src/term_out.cpp | 4 +-- src/term_out.h | 4 +-- src/themes.cpp | 14 ++++----- src/themes.h | 12 ++++---- src/utils.cpp | 6 ++-- src/utils.h | 18 +++++------ 27 files changed, 87 insertions(+), 112 deletions(-) rename include/{unordered_dense.h => ankerl_unordered_dense.h} (100%) rename libgw/{ => test}/Makefile (89%) rename libgw/{ => test}/test.cpp (58%) diff --git a/Makefile b/Makefile index 532d499..9546165 100644 --- a/Makefile +++ b/Makefile @@ -37,21 +37,23 @@ ifdef HTSLIB endif SKIA ?= "" +SKIA_PATH="" ifneq ($(PLATFORM), "Windows") ifneq ($(SKIA),"") CPPFLAGS += -I$(SKIA) - LDFLAGS += -L $(wildcard $(SKIA)/out/Rel*) + SKIA_PATH = $(wildcard $(SKIA)/out/Rel*) else ifeq ($(PLATFORM),"Darwin") CPPFLAGS += -I./lib/skia - LDFLAGS += -L./lib/skia/out/Release-x64 + SKIA_PATH = ./lib/skia/out/Release-x64 else ifeq ($(PLATFORM),"Arm64") CPPFLAGS += -I./lib/skia - LDFLAGS += -L./lib/skia/out/Release-arm64 + SKIA_PATH = ./lib/skia/out/Release-arm64 else CPPFLAGS += -I./lib/skia - LDFLAGS += -L./lib/skia/out/Release-x64 + SKIA_PATH = ./lib/skia/out/Release-x64 endif endif +LDFLAGS += -L$(SKIA_PATH) SKIA_LINK="" USE_GL ?= "" # Else use EGL backend for Linux only @@ -81,8 +83,7 @@ CPPFLAGS += -I./lib/libBigWig -I./include -I. $(LIBGW_INCLUDE) -I./src LDLIBS += -lskia -lm -ljpeg -lpng -lsvg -lhts -lfontconfig -lpthread -# set platform flags and libs -ifeq ($(PLATFORM),"Linux") +ifeq ($(PLATFORM),"Linux") # set platform flags and libs ifeq (${XDG_SESSION_TYPE},"wayland") # wayland is untested! LDLIBS += -lwayland-client else @@ -124,7 +125,6 @@ endif OBJECTS = $(patsubst %.cpp, %.o, $(wildcard ./src/*.cpp)) OBJECTS += $(patsubst %.c, %.o, $(wildcard ./lib/libBigWig/*.c)) - debug: CXXFLAGS += -g debug: LDFLAGS += -fsanitize=address -fsanitize=undefined @@ -140,10 +140,10 @@ clean: ifeq ($(UNAME_S),Linux) - SHARED_TARGET = libgw.so + SHARED_TARGET = libgw/libgw.so endif ifeq ($(UNAME_S),Darwin) - SHARED_TARGET = libgw.dylib + SHARED_TARGET = libgw/libgw.dylib endif shared: CXXFLAGS += -fPIC -DBUILDING_LIBGW @@ -154,6 +154,7 @@ shared: $(OBJECTS) -cp include/*.h* libgw/include -cp lib/libBigWig/*.h libgw/include -cp -rf lib/skia/include libgw/include + -cp $(SKIA_PATH)/libskia.a libgw ifeq ($(UNAME_S),Darwin) $(CXX) $(OBJECTS) $(LDFLAGS) $(LDLIBS) -dynamiclib -DBUILDING_LIBGW -o $(SHARED_TARGET) diff --git a/include/unordered_dense.h b/include/ankerl_unordered_dense.h similarity index 100% rename from include/unordered_dense.h rename to include/ankerl_unordered_dense.h diff --git a/libgw/libgw.hpp b/libgw/libgw.hpp index f4a1ab2..4cf09d8 100644 --- a/libgw/libgw.hpp +++ b/libgw/libgw.hpp @@ -1,31 +1,9 @@ -// -// Created by Kez Cleal on 28/02/2024. -// #pragma once -#include "themes.h" #include "export_definitions.h" - - -namespace Gw { - - enum GwPaint { - bgPaint, fcNormal, fcDel, fcDup, fcInvF, fcInvR, fcTra, fcIns, fcSoftClip, - fcA, fcT, fcC, fcG, fcN, fcCoverage, fcTrack, fcNormal0, fcDel0, fcDup0, fcInvF0, fcInvR0, fcTra0, - fcSoftClip0, fcBigWig, mate_fc, mate_fc0, ecMateUnmapped, ecSplit, ecSelected, - lcJoins, lcCoverage, lcLightJoins, insF, insS, lcLabel, lcBright, tcDel, tcIns, tcLabels, tcBackground, - marker_paint - }; - - class EXPORT BaseTheme { - public: - BaseTheme(); - ~BaseTheme() = default; - - void setAlphas(); - void setPaintARGB(int paint_enum, int alpha, int red, int green, int blue); - - }; -} +#include "themes.h" +#include "plot_manager.h" +#include "utils.h" +#include "segments.h" diff --git a/libgw/Makefile b/libgw/test/Makefile similarity index 89% rename from libgw/Makefile rename to libgw/test/Makefile index 08e6f9a..969e28f 100644 --- a/libgw/Makefile +++ b/libgw/test/Makefile @@ -1,8 +1,8 @@ CXXFLAGS += -std=c++17 -DOSX -stdlib=libc++ -arch arm64 -mmacosx-version-min=10.15 -CPPFLAGS += -I./include +CPPFLAGS += -I../ -I../include LDFLAGS += -L../ -undefined dynamic_lookup -framework OpenGL -framework AppKit -framework ApplicationServices -LDLIBS += -lgw +LDLIBS += -lskia -lgw CXX=clang++ diff --git a/libgw/test.cpp b/libgw/test/test.cpp similarity index 58% rename from libgw/test.cpp rename to libgw/test/test.cpp index 743ac29..8b94531 100644 --- a/libgw/test.cpp +++ b/libgw/test/test.cpp @@ -1,13 +1,11 @@ // // Created by Kez Cleal on 29/02/2024. // + #include #include "libgw.hpp" int main(int argc, char *argv[]) { - Gw::BaseTheme t; - - int p = Gw::GwPaint::fcNormal; - t.setPaintARGB(p, 100, 100, 100, 100); + Themes::BaseTheme t; return 0; } \ No newline at end of file diff --git a/src/drawing.cpp b/src/drawing.cpp index 0903a2a..ba9a91d 100644 --- a/src/drawing.cpp +++ b/src/drawing.cpp @@ -21,8 +21,8 @@ #include "include/core/SkTextBlob.h" #include "htslib/sam.h" -#include "../include/BS_thread_pool.h" -#include "../include/unordered_dense.h" +#include "BS_thread_pool.h" +#include "ankerl_unordered_dense.h" #include "hts_funcs.h" #include "drawing.h" diff --git a/src/drawing.h b/src/drawing.h index 333cebc..1a166f1 100644 --- a/src/drawing.h +++ b/src/drawing.h @@ -19,8 +19,8 @@ #include "include/core/SkColorSpace.h" #include "include/core/SkSurface.h" -#include "../include/BS_thread_pool.h" -#include "../include/unordered_dense.h" +#include "BS_thread_pool.h" +#include "ankerl_unordered_dense.h" #include "hts_funcs.h" #include "utils.h" diff --git a/src/glfw_keys.cpp b/src/glfw_keys.cpp index 3099fb0..ca2cada 100644 --- a/src/glfw_keys.cpp +++ b/src/glfw_keys.cpp @@ -5,7 +5,7 @@ #include #include #include -#include "../include/unordered_dense.h" +#include "ankerl_unordered_dense.h" namespace Keys { diff --git a/src/glfw_keys.h b/src/glfw_keys.h index 84fae4e..43c27a3 100644 --- a/src/glfw_keys.h +++ b/src/glfw_keys.h @@ -6,7 +6,7 @@ #include #include #include -#include "../include/unordered_dense.h" +#include "ankerl_unordered_dense.h" namespace Keys { diff --git a/src/hts_funcs.cpp b/src/hts_funcs.cpp index 502d219..7143c79 100644 --- a/src/hts_funcs.cpp +++ b/src/hts_funcs.cpp @@ -14,11 +14,11 @@ #include "htslib/tbx.h" #include "htslib/vcf.h" -#include "../include/BS_thread_pool.h" -#include "../include/termcolor.h" -#include "../lib/libBigWig/bigWig.h" -#include "../include/glob_cpp.hpp" -#include "../include/natsort.hpp" +#include "BS_thread_pool.h" +#include "termcolor.h" +#include "bigWig.h" +#include "glob_cpp.hpp" +#include "natsort.hpp" #include "drawing.h" #include "segments.h" #include "themes.h" diff --git a/src/hts_funcs.h b/src/hts_funcs.h index b0cf3c6..ff02c7a 100644 --- a/src/hts_funcs.h +++ b/src/hts_funcs.h @@ -15,12 +15,12 @@ #include "htslib/tbx.h" #include "parser.h" -#include "../include/IITree.h" -#include "../include/unordered_dense.h" +#include "IITree.h" +#include "ankerl_unordered_dense.h" #include "bigWig.h" //#include "../lib/libBigWig/bigWig.h" -#include "../include/glob_cpp.hpp" +#include "glob_cpp.hpp" #include "segments.h" #include "themes.h" diff --git a/src/main.cpp b/src/main.cpp index 3bc8535..965f98f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -9,9 +9,9 @@ #include #include #include "argparse.h" -#include "../include/BS_thread_pool.h" +#include "BS_thread_pool.h" //#include "../include/natsort.hpp" -#include "../include/glob_cpp.hpp" +#include "glob_cpp.hpp" #include "hts_funcs.h" #include "parser.h" #include "plot_manager.h" diff --git a/src/menu.cpp b/src/menu.cpp index 2619c1d..0824e37 100644 --- a/src/menu.cpp +++ b/src/menu.cpp @@ -32,9 +32,9 @@ #include "glfw_keys.h" #include "plot_manager.h" #include "segments.h" -#include "../include/ini.h" -#include "../include/unordered_dense.h" -#include "../include/termcolor.h" +#include "ini.h" +#include "ankerl_unordered_dense.h" +#include "termcolor.h" #include "themes.h" #include "utils.h" #include "menu.h" diff --git a/src/menu.h b/src/menu.h index 1dd7800..87361fd 100644 --- a/src/menu.h +++ b/src/menu.h @@ -21,7 +21,7 @@ #include "hts_funcs.h" #include "plot_manager.h" #include "parser.h" -#include "../include/unordered_dense.h" +#include "ankerl_unordered_dense.h" #include "utils.h" #include "segments.h" #include "themes.h" diff --git a/src/parser.cpp b/src/parser.cpp index 84aeb29..0ce02dc 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -7,8 +7,8 @@ #include #include #include -#include "../include/unordered_dense.h" -#include "../include/termcolor.h" +#include "ankerl_unordered_dense.h" +#include "termcolor.h" #include "utils.h" #include "parser.h" #include "segments.h" diff --git a/src/parser.h b/src/parser.h index c1bb0d1..561368e 100644 --- a/src/parser.h +++ b/src/parser.h @@ -12,7 +12,7 @@ #include #include "segments.h" -#include "../include/unordered_dense.h" +#include "ankerl_unordered_dense.h" namespace Parse { diff --git a/src/plot_controls.cpp b/src/plot_controls.cpp index 32780a1..9532e42 100644 --- a/src/plot_controls.cpp +++ b/src/plot_controls.cpp @@ -19,7 +19,7 @@ #include "plot_manager.h" #include "menu.h" #include "segments.h" -#include "../include/termcolor.h" +#include "termcolor.h" #include "term_out.h" #include "themes.h" diff --git a/src/plot_manager.cpp b/src/plot_manager.cpp index 963711a..21a7f96 100644 --- a/src/plot_manager.cpp +++ b/src/plot_manager.cpp @@ -30,14 +30,14 @@ #include "include/core/SkSurface.h" #include "include/core/SkDocument.h" -#include "../include/unordered_dense.h" +#include "ankerl_unordered_dense.h" #include "drawing.h" #include "plot_manager.h" #include "menu.h" #include "segments.h" -#include "../include/termcolor.h" +#include "termcolor.h" #include "themes.h" -#include "../include/window_icon.h" +#include "window_icon.h" using namespace std::literals; diff --git a/src/plot_manager.h b/src/plot_manager.h index be90f0a..0d38ac4 100644 --- a/src/plot_manager.h +++ b/src/plot_manager.h @@ -25,8 +25,8 @@ #include #include -#include "../include/unordered_dense.h" -#include "../include/BS_thread_pool.h" +#include "ankerl_unordered_dense.h" +#include "BS_thread_pool.h" #include "drawing.h" #include "glfw_keys.h" #include "hts_funcs.h" @@ -89,6 +89,8 @@ namespace Manager { std::string reference; + std::string inputText; + std::vector bam_paths; std::vector bams; std::vector headers; @@ -191,6 +193,8 @@ namespace Manager { int printRegionInfo(); + bool commandProcessed(); + private: long frameId; @@ -201,7 +205,6 @@ namespace Manager { bool triggerClose; std::chrono::high_resolution_clock::time_point resizeTimer, regionTimer; - std::string inputText; std::string target_qname; std::string cursorGenomePos; @@ -250,8 +253,6 @@ namespace Manager { int registerKey(GLFWwindow* window, int key, int scancode, int action, int mods); - bool commandProcessed(); - void updateSettings(); int getCollectionIdx(float x, float y); diff --git a/src/segments.cpp b/src/segments.cpp index aee5ce9..40fa32d 100644 --- a/src/segments.cpp +++ b/src/segments.cpp @@ -376,7 +376,7 @@ namespace Segs { } } - ReadCollection::ReadCollection() { + EXPORT ReadCollection::ReadCollection() { vScroll = 0; collection_processed = false; skipDrawingReads = false; diff --git a/src/segments.h b/src/segments.h index 921d6da..4acffe0 100644 --- a/src/segments.h +++ b/src/segments.h @@ -10,17 +10,18 @@ #include #include -#include "../include/BS_thread_pool.h" -#include "../include/unordered_dense.h" +#include "BS_thread_pool.h" +#include "ankerl_unordered_dense.h" #include "htslib/sam.h" +#include "export_definitions.h" #include "themes.h" #include "utils.h" namespace Segs { - enum Pattern { + EXPORT enum Pattern { u = 0, NORMAL = 0, DEL = 1, @@ -32,7 +33,7 @@ namespace Segs { // typedef int64_t hts_pos_t; - struct InsItem { + EXPORT struct InsItem { uint32_t pos, length; }; // @@ -54,7 +55,7 @@ namespace Segs { // // void get_mismatched_bases(std::vector &result, const char *md_tag, uint32_t r_pos, uint32_t ct_l, uint32_t *cigar_p); - struct Align { + EXPORT struct Align { bam1_t *delegate; int cov_start, cov_end, orient_pattern, left_soft_clip, right_soft_clip, y, edge_type; uint32_t pos, reference_end; @@ -67,13 +68,13 @@ namespace Segs { } }; - struct Mismatches { + EXPORT struct Mismatches { uint32_t A, T, C, G; }; typedef ankerl::unordered_dense::map< std::string, std::vector< Align* >> map_t; - class ReadCollection { + class EXPORT ReadCollection { public: ReadCollection(); ~ReadCollection() = default; diff --git a/src/term_out.cpp b/src/term_out.cpp index a70a4f8..e4dc016 100644 --- a/src/term_out.cpp +++ b/src/term_out.cpp @@ -20,8 +20,8 @@ #include "hts_funcs.h" #include "plot_manager.h" #include "segments.h" -#include "../include/unordered_dense.h" -#include "../include/termcolor.h" +#include "ankerl_unordered_dense.h" +#include "termcolor.h" #include "term_out.h" #include "themes.h" diff --git a/src/term_out.h b/src/term_out.h index adef7e0..71165e2 100644 --- a/src/term_out.h +++ b/src/term_out.h @@ -13,8 +13,8 @@ #include "hts_funcs.h" #include "plot_manager.h" #include "segments.h" -#include "../include/unordered_dense.h" -#include "../include/termcolor.h" +#include "ankerl_unordered_dense.h" +#include "termcolor.h" #include "term_out.h" #include "themes.h" diff --git a/src/themes.cpp b/src/themes.cpp index 1ed8d75..8c17f39 100644 --- a/src/themes.cpp +++ b/src/themes.cpp @@ -6,13 +6,12 @@ #include "themes.h" #include "glfw_keys.h" #include "defaultIni.hpp" -#include "unordered_dense.h" +#include "ankerl_unordered_dense.h" namespace Themes { EXPORT BaseTheme::BaseTheme() { -// BaseTheme::BaseTheme() { fcCoverage.setStyle(SkPaint::kStrokeAndFill_Style); fcCoverage.setStrokeWidth(0); @@ -233,7 +232,7 @@ namespace Themes { } } - IgvTheme::IgvTheme() { + EXPORT IgvTheme::IgvTheme() { name = "igv"; fcCoverage.setARGB(255, 195, 195, 195); fcTrack.setARGB(200, 0, 0, 0); @@ -268,7 +267,7 @@ namespace Themes { ecSplit.setStrokeWidth(1); } - DarkTheme::DarkTheme() { + EXPORT DarkTheme::DarkTheme() { name = "dark"; fcCoverage.setARGB(255, 95, 95, 105); fcTrack.setARGB(200, 227, 232, 255); @@ -305,7 +304,7 @@ namespace Themes { ecSplit.setStrokeWidth(1); } - SlateTheme::SlateTheme() { + EXPORT SlateTheme::SlateTheme() { name = "slate"; fcCoverage.setARGB(255, 103, 102, 109); fcTrack.setARGB(200, 227, 232, 255); @@ -341,8 +340,7 @@ namespace Themes { ecSplit.setStrokeWidth(1); } -// EXPORT IniOptions::IniOptions() { - IniOptions::IniOptions() { + EXPORT IniOptions::IniOptions() { menu_level = ""; menu_table = MAIN; theme_str = "dark"; @@ -583,7 +581,7 @@ namespace Themes { const SkGlyphID glyphs[1] = {100}; - Fonts::Fonts() { + EXPORT Fonts::Fonts() { rect = SkRect::MakeEmpty(); path = SkPath(); fontMaxSize = 35; // in pixels diff --git a/src/themes.h b/src/themes.h index 81a70b2..a558603 100644 --- a/src/themes.h +++ b/src/themes.h @@ -77,7 +77,6 @@ namespace Themes { }; class EXPORT BaseTheme { -// class BaseTheme { public: BaseTheme(); ~BaseTheme() = default; @@ -115,26 +114,25 @@ namespace Themes { }; - class IgvTheme: public BaseTheme { + class EXPORT IgvTheme: public BaseTheme { public: IgvTheme(); ~IgvTheme() = default; }; - class DarkTheme: public BaseTheme { + class EXPORT DarkTheme: public BaseTheme { public: DarkTheme(); ~DarkTheme() = default; }; - class SlateTheme: public BaseTheme { + class EXPORT SlateTheme: public BaseTheme { public: SlateTheme(); ~SlateTheme() = default; }; -// class EXPORT IniOptions { - class IniOptions { + class EXPORT IniOptions { public: IniOptions(); ~IniOptions() {}; @@ -178,7 +176,7 @@ namespace Themes { }; - class Fonts { + class EXPORT Fonts { public: Fonts(); ~Fonts() = default; diff --git a/src/utils.cpp b/src/utils.cpp index 3f2a00e..850af1a 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -15,7 +15,7 @@ #include #include -#include "../include/unordered_dense.h" +#include "ankerl_unordered_dense.h" #include "utils.h" #include "htslib/faidx.h" @@ -175,7 +175,7 @@ namespace Utils { } } - Region parseRegion(std::string &s) { + EXPORT Region parseRegion(std::string &s) { Region reg; std::string s2; if (s.find(":") != std::string::npos) { @@ -363,7 +363,7 @@ namespace Utils { } - Dims parseDimensions(std::string &s) { + EXPORT Dims parseDimensions(std::string &s) { Dims d = {0, 0}; int start = 0; int end = (int)s.find('x'); diff --git a/src/utils.h b/src/utils.h index d2f8d7b..68f46eb 100644 --- a/src/utils.h +++ b/src/utils.h @@ -7,8 +7,8 @@ #include #include #include -#include "../include/unordered_dense.h" - +#include "ankerl_unordered_dense.h" +#include "export_definitions.h" #include "htslib/faidx.h" #if defined(_WIN32) @@ -37,7 +37,7 @@ namespace Utils { bool is_file_exist(std::string FileName); - class TrackBlock { + class EXPORT TrackBlock { public: std::string chrom, name, line, vartype, parent; int start, end; @@ -58,7 +58,7 @@ namespace Utils { } }; - class GFFTrackBlock { + class EXPORT GFFTrackBlock { public: std::string chrom, name, line, vartype; std::vector parts; @@ -66,7 +66,7 @@ namespace Utils { int strand; // 0 is none, 1 forward, 2 reverse }; - class Region { + class EXPORT Region { public: std::string chrom; int start, end; @@ -84,7 +84,7 @@ namespace Utils { } }; - Region parseRegion(std::string &r); + EXPORT Region parseRegion(std::string &r); bool parseFilenameToRegions(std::filesystem::path &path, std::vector ®ions, faidx_t* fai, int pad, int split_size); @@ -98,11 +98,11 @@ namespace Utils { FileNameInfo parseFilenameInfo(std::filesystem::path &path); - struct Dims { + EXPORT struct Dims { int x, y; }; - Dims parseDimensions(std::string &s); + EXPORT Dims parseDimensions(std::string &s); int intervalOverlap(int start1, int end1, int start2, int end2); @@ -114,7 +114,7 @@ namespace Utils { std::vector imageBoundingBoxes(Dims &dims, float wndowWidth, float windowHeight, float padX=15, float padY=15, float ySpace=0); - class Label { + class EXPORT Label { public: Label() = default; ~Label() = default; From 7a620d55a374303d5347eecd3208aab9c86112f7 Mon Sep 17 00:00:00 2001 From: kcleal Date: Fri, 1 Mar 2024 16:34:48 +0000 Subject: [PATCH 10/51] updated shared --- Makefile | 17 +++++++++-------- libgw/libgw.hpp | 16 ++++++++-------- src/segments.h | 8 ++++---- src/utils.h | 2 +- 4 files changed, 22 insertions(+), 21 deletions(-) diff --git a/Makefile b/Makefile index 9546165..a0f4758 100644 --- a/Makefile +++ b/Makefile @@ -140,24 +140,25 @@ clean: ifeq ($(UNAME_S),Linux) - SHARED_TARGET = libgw/libgw.so + SHARED_TARGET = libgw.so endif ifeq ($(UNAME_S),Darwin) - SHARED_TARGET = libgw/libgw.dylib + SHARED_TARGET = libgw.dylib endif shared: CXXFLAGS += -fPIC -DBUILDING_LIBGW shared: $(OBJECTS) - -mkdir -p libgw/include - -cp src/*.h libgw/include - -cp include/*.h* libgw/include - -cp lib/libBigWig/*.h libgw/include - -cp -rf lib/skia/include libgw/include - -cp $(SKIA_PATH)/libskia.a libgw ifeq ($(UNAME_S),Darwin) $(CXX) $(OBJECTS) $(LDFLAGS) $(LDLIBS) -dynamiclib -DBUILDING_LIBGW -o $(SHARED_TARGET) else $(CXX) $(OBJECTS) $(LDFLAGS) $(LDLIBS) -shared -DBUILDING_LIBGW -o $(SHARED_TARGET) endif + -mkdir -p libgw/include + -cp src/*.h libgw/include + -cp include/*.h* libgw/include + -cp lib/libBigWig/*.h libgw/include + -cp -rf lib/skia/include libgw/include + -cp $(SKIA_PATH)/libskia.a libgw + -mv $(SHARED_TARGET) libgw diff --git a/libgw/libgw.hpp b/libgw/libgw.hpp index 4cf09d8..e19a477 100644 --- a/libgw/libgw.hpp +++ b/libgw/libgw.hpp @@ -1,9 +1,9 @@ -#pragma once - -#include "export_definitions.h" -#include "themes.h" -#include "plot_manager.h" -#include "utils.h" -#include "segments.h" - +//#pragma once +// +//#include "export_definitions.h" +//#include "themes.h" +//#include "plot_manager.h" +//#include "utils.h" +//#include "segments.h" +// diff --git a/src/segments.h b/src/segments.h index 4acffe0..742c380 100644 --- a/src/segments.h +++ b/src/segments.h @@ -21,7 +21,7 @@ namespace Segs { - EXPORT enum Pattern { + enum EXPORT Pattern { u = 0, NORMAL = 0, DEL = 1, @@ -33,7 +33,7 @@ namespace Segs { // typedef int64_t hts_pos_t; - EXPORT struct InsItem { + struct EXPORT InsItem { uint32_t pos, length; }; // @@ -55,7 +55,7 @@ namespace Segs { // // void get_mismatched_bases(std::vector &result, const char *md_tag, uint32_t r_pos, uint32_t ct_l, uint32_t *cigar_p); - EXPORT struct Align { + struct EXPORT Align { bam1_t *delegate; int cov_start, cov_end, orient_pattern, left_soft_clip, right_soft_clip, y, edge_type; uint32_t pos, reference_end; @@ -68,7 +68,7 @@ namespace Segs { } }; - EXPORT struct Mismatches { + struct EXPORT Mismatches { uint32_t A, T, C, G; }; diff --git a/src/utils.h b/src/utils.h index 68f46eb..6a2f6f3 100644 --- a/src/utils.h +++ b/src/utils.h @@ -98,7 +98,7 @@ namespace Utils { FileNameInfo parseFilenameInfo(std::filesystem::path &path); - EXPORT struct Dims { + struct EXPORT Dims { int x, y; }; From 4e17826de8cd5d36932368030c34293e1ab24c7a Mon Sep 17 00:00:00 2001 From: kcleal Date: Fri, 1 Mar 2024 20:30:01 +0000 Subject: [PATCH 11/51] updated shared --- include/libgwplot.hpp | 9 +++++++++ {libgw => lib/libgw}/test/Makefile | 7 ++++--- {libgw => lib/libgw}/test/test.cpp | 2 +- 3 files changed, 14 insertions(+), 4 deletions(-) create mode 100644 include/libgwplot.hpp rename {libgw => lib/libgw}/test/Makefile (59%) rename {libgw => lib/libgw}/test/test.cpp (85%) diff --git a/include/libgwplot.hpp b/include/libgwplot.hpp new file mode 100644 index 0000000..4cf09d8 --- /dev/null +++ b/include/libgwplot.hpp @@ -0,0 +1,9 @@ + +#pragma once + +#include "export_definitions.h" +#include "themes.h" +#include "plot_manager.h" +#include "utils.h" +#include "segments.h" + diff --git a/libgw/test/Makefile b/lib/libgw/test/Makefile similarity index 59% rename from libgw/test/Makefile rename to lib/libgw/test/Makefile index 969e28f..666afb0 100644 --- a/libgw/test/Makefile +++ b/lib/libgw/test/Makefile @@ -1,8 +1,8 @@ -CXXFLAGS += -std=c++17 -DOSX -stdlib=libc++ -arch arm64 -mmacosx-version-min=10.15 +CXXFLAGS += -std=c++17 -DOSX -stdlib=libc++ -arch arm64 -mmacosx-version-min=11 CPPFLAGS += -I../ -I../include -LDFLAGS += -L../ -undefined dynamic_lookup -framework OpenGL -framework AppKit -framework ApplicationServices -LDLIBS += -lskia -lgw +LDFLAGS += -L../out -undefined dynamic_lookup -framework OpenGL -framework AppKit -framework ApplicationServices +LDLIBS += -lskia -lgwplot CXX=clang++ @@ -18,6 +18,7 @@ SOURCES=$(wildcard *.cpp) hello: $(OBJECTS) $(CXX) $(CXXFLAGS) $(CPPFLAGS) -g $(SOURCES) $(LDFLAGS) $(LDLIBS) -o $@ + -install_name_tool -change libgwplot.dylib @loader_path/../out/libgwplot.dylib hello clean: -rm -rf *.o ./*.o ./*.dSYM diff --git a/libgw/test/test.cpp b/lib/libgw/test/test.cpp similarity index 85% rename from libgw/test/test.cpp rename to lib/libgw/test/test.cpp index 8b94531..b0aaf16 100644 --- a/libgw/test/test.cpp +++ b/lib/libgw/test/test.cpp @@ -3,7 +3,7 @@ // #include -#include "libgw.hpp" +#include "libgwplot.hpp" int main(int argc, char *argv[]) { Themes::BaseTheme t; From 11e9a912b954ead80e4082682c50b9735a53590a Mon Sep 17 00:00:00 2001 From: kcleal Date: Fri, 1 Mar 2024 20:33:11 +0000 Subject: [PATCH 12/51] Updated build --- .gitignore | 2 +- Makefile | 14 +++++++------- lib/libgw/test/Makefile | 6 +++--- libgw/libgw.hpp | 9 --------- 4 files changed, 11 insertions(+), 20 deletions(-) delete mode 100644 libgw/libgw.hpp diff --git a/.gitignore b/.gitignore index 9877ece..a6074bc 100644 --- a/.gitignore +++ b/.gitignore @@ -33,6 +33,6 @@ /lib/libBigWig/bwValues.o /lib/libBigWig/bwWrite.o /lib/libBigWig/io.o -/libgw/ +/lib/libgw/ libgw.so /libgw.dylib diff --git a/Makefile b/Makefile index a0f4758..c3e0b63 100644 --- a/Makefile +++ b/Makefile @@ -155,10 +155,10 @@ ifeq ($(UNAME_S),Darwin) else $(CXX) $(OBJECTS) $(LDFLAGS) $(LDLIBS) -shared -DBUILDING_LIBGW -o $(SHARED_TARGET) endif - -mkdir -p libgw/include - -cp src/*.h libgw/include - -cp include/*.h* libgw/include - -cp lib/libBigWig/*.h libgw/include - -cp -rf lib/skia/include libgw/include - -cp $(SKIA_PATH)/libskia.a libgw - -mv $(SHARED_TARGET) libgw + -mkdir -p lib/libgw/include lib/libgw/out + -cp $(SKIA_PATH)/libskia.a lib/libgw/out + -cp src/*.h lib/libgw/include + -cp include/*.h* lib/libgw/include + -mv $(SHARED_TARGET) lib/libgw/out + + diff --git a/lib/libgw/test/Makefile b/lib/libgw/test/Makefile index 666afb0..7a671e8 100644 --- a/lib/libgw/test/Makefile +++ b/lib/libgw/test/Makefile @@ -1,8 +1,8 @@ CXXFLAGS += -std=c++17 -DOSX -stdlib=libc++ -arch arm64 -mmacosx-version-min=11 -CPPFLAGS += -I../ -I../include +CPPFLAGS += -I../include -I../../skia/ -I../../libBigWig LDFLAGS += -L../out -undefined dynamic_lookup -framework OpenGL -framework AppKit -framework ApplicationServices -LDLIBS += -lskia -lgwplot +LDLIBS += -lskia -lgw CXX=clang++ @@ -18,7 +18,7 @@ SOURCES=$(wildcard *.cpp) hello: $(OBJECTS) $(CXX) $(CXXFLAGS) $(CPPFLAGS) -g $(SOURCES) $(LDFLAGS) $(LDLIBS) -o $@ - -install_name_tool -change libgwplot.dylib @loader_path/../out/libgwplot.dylib hello + -install_name_tool -change libgw.dylib @loader_path/../out/libgw.dylib hello clean: -rm -rf *.o ./*.o ./*.dSYM diff --git a/libgw/libgw.hpp b/libgw/libgw.hpp deleted file mode 100644 index e19a477..0000000 --- a/libgw/libgw.hpp +++ /dev/null @@ -1,9 +0,0 @@ - -//#pragma once -// -//#include "export_definitions.h" -//#include "themes.h" -//#include "plot_manager.h" -//#include "utils.h" -//#include "segments.h" -// From 87898244c5a6eaf0834696da0b184123b20e70e3 Mon Sep 17 00:00:00 2001 From: kcleal Date: Sun, 3 Mar 2024 20:56:14 +0000 Subject: [PATCH 13/51] updated shared lib --- src/plot_controls.cpp | 191 +++++++++++++++++++++++------------------- src/plot_manager.h | 8 ++ 2 files changed, 113 insertions(+), 86 deletions(-) diff --git a/src/plot_controls.cpp b/src/plot_controls.cpp index 9532e42..434a3f0 100644 --- a/src/plot_controls.cpp +++ b/src/plot_controls.cpp @@ -416,6 +416,64 @@ namespace Manager { return key; } + void GwPlot::removeBam(int index) { + if (index >= (int) bams.size()) { + std::cerr << termcolor::red << "Error:" << termcolor::reset << " bam index is out of range. Use 0-based indexing\n"; + return; + } + collections.erase(std::remove_if(collections.begin(), collections.end(), [&index](const auto &col) { + return col.bamIdx == index; + }), collections.end()); + bams.erase(bams.begin() + index, bams.begin() + index + 1); + indexes.erase(indexes.begin() + index, indexes.begin() + index + 1); + headers.erase(headers.begin() + index, headers.begin() + index + 1); + processed = false; + redraw = true; + inputText = ""; + imageCache.clear(); + } + + void GwPlot::removeTrack(int index) { + if (index >= (int)tracks.size()) { + std::cerr << termcolor::red << "Error:" << termcolor::reset << " track index is out of range. Use 0-based indexing\n"; + return; + } + for (auto &rgn : regions) { + rgn.featuresInView.clear(); + rgn.featureLevels.clear(); + } + tracks[index].close(); + tracks.erase(tracks.begin() + index, tracks.begin() + index + 1); + for (auto &trk: tracks) { + trk.open(trk.path, true); + } + processed = false; + redraw = true; + inputText = ""; + imageCache.clear(); + } + + void GwPlot::removeRegion(int index) { + regionSelection = 0; + if (!regions.empty() && index < (int)regions.size()) { + if (regions.size() == 1 && index == 0) { + regions.clear(); + } else { + regions.erase(regions.begin() + index); + } + } else { + std::cerr << termcolor::red << "Error:" << termcolor::reset << " region index is out of range. Use 0-based indexing\n"; + return; + } + collections.erase(std::remove_if(collections.begin(), collections.end(), [&index](const auto col) { + return col.regionIdx == index; + }), collections.end()); + processed = false; + redraw = true; + inputText = ""; + imageCache.clear(); + } + void GwPlot::highlightQname() { // todo make this more efficient for (auto &cl : collections) { for (auto &a: cl.readQueue) { @@ -712,21 +770,7 @@ namespace Manager { inputText = ""; return true; } - if (ind >= (int) bams.size()) { - std::cerr << termcolor::red << "Error:" << termcolor::reset - << " bam index is out of range. Use 0-based indexing\n"; - return true; - } - collections.erase(std::remove_if(collections.begin(), collections.end(), [&ind](const auto &col) { - return col.bamIdx == ind; - }), collections.end()); - bams.erase(bams.begin() + ind, bams.begin() + ind + 1); - indexes.erase(indexes.begin() + ind, indexes.begin() + ind + 1); - headers.erase(headers.begin() + ind, headers.begin() + ind + 1); - processed = false; - redraw = true; - inputText = ""; - imageCache.clear(); + removeBam(ind); return true; } else if (Utils::startsWith(split.back(), "track")) { split.back().erase(0, 5); @@ -737,23 +781,7 @@ namespace Manager { inputText = ""; return true; } - if (ind >= (int)tracks.size()) { - std::cerr << termcolor::red << "Error:" << termcolor::reset << " track index is out of range. Use 0-based indexing\n"; - return true; - } - for (auto &rgn : regions) { - rgn.featuresInView.clear(); - rgn.featureLevels.clear(); - } - tracks[ind].close(); - tracks.erase(tracks.begin() + ind, tracks.begin() + ind + 1); - for (auto &trk: tracks) { - trk.open(trk.path, true); - } - processed = false; - redraw = true; - inputText = ""; - imageCache.clear(); + removeTrack(ind); return true; } else { try { @@ -763,24 +791,7 @@ namespace Manager { inputText = ""; return true; } - regionSelection = 0; - if (!regions.empty() && ind < (int)regions.size()) { - if (regions.size() == 1 && ind == 0) { - regions.clear(); - } else { - regions.erase(regions.begin() + ind); - } - } else { - std::cerr << termcolor::red << "Error:" << termcolor::reset << " region index is out of range. Use 0-based indexing\n"; - return true; - } - collections.erase(std::remove_if(collections.begin(), collections.end(), [&ind](const auto col) { - return col.regionIdx == ind; - }), collections.end()); - processed = false; - redraw = true; - inputText = ""; - imageCache.clear(); + removeRegion(ind); return true; } @@ -1782,47 +1793,48 @@ namespace Manager { } } - void GwPlot::pathDrop(GLFWwindow* wind, int count, const char** paths) { + void GwPlot::addTrack(std::string &path, bool print_message=true) { bool good = false; - - for (int i=0; i < count; ++ i) { - std::string pth = *paths; - if (Utils::endsWith(pth, ".bam") || Utils::endsWith(pth, ".cram")) { - good = true; - std::cout << termcolor::magenta << "\nAlignments " << termcolor::reset << pth << "\n"; - bam_paths.push_back(pth); - htsFile* f = sam_open(pth.c_str(), "r"); - 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, pth.c_str()); - indexes.push_back(idx); - } else if (!opts.vcf_as_tracks && (Utils::endsWith(pth, ".vcf.gz") || Utils::endsWith(pth, ".vcf") || Utils::endsWith(pth, ".bcf"))) { - good = true; - std::vector labels = Utils::split(opts.labels, ','); - setLabelChoices(labels); - mouseOverTileIndex = 0; - bboxes = Utils::imageBoundingBoxes(opts.number, (float) fb_width, (float) fb_height); - imageCache.clear(); - addVariantTrack(pth, opts.start_index, false, false); - variantFileSelection = (int) variantTracks.size() - 1; - currentVarTrack = &variantTracks[variantFileSelection]; - currentVarTrack->blockStart = 0; - mode = Manager::Show::TILED; + if (Utils::endsWith(path, ".bam") || Utils::endsWith(path, ".cram")) { + good = true; + if (print_message) { + std::cout << termcolor::magenta << "\nAlignments " << termcolor::reset << path << "\n"; + } + bam_paths.push_back(path); + htsFile* f = sam_open(path.c_str(), "r"); + 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, 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"))) { + good = true; + std::vector labels = Utils::split(opts.labels, ','); + setLabelChoices(labels); + mouseOverTileIndex = 0; + bboxes = Utils::imageBoundingBoxes(opts.number, (float) fb_width, (float) fb_height); + imageCache.clear(); + addVariantTrack(path, opts.start_index, false, false); + variantFileSelection = (int) variantTracks.size() - 1; + currentVarTrack = &variantTracks[variantFileSelection]; + currentVarTrack->blockStart = 0; + mode = Manager::Show::TILED; + if (print_message) { std::cout << termcolor::magenta << "\nFile " << termcolor::reset << variantTracks[variantFileSelection].path << "\n"; - } else { - tracks.push_back(HGW::GwTrack()); - try { - tracks.back().open(pth, true); - tracks.back().variant_distance = &opts.variant_distance; - std::cout << termcolor::magenta << "\nTrack " << termcolor::reset << pth << "\n"; - } catch (...) { - tracks.pop_back(); + } + } else { + tracks.push_back(HGW::GwTrack()); + try { + tracks.back().open(path, true); + tracks.back().variant_distance = &opts.variant_distance; + if (print_message) { + std::cout << termcolor::magenta << "\nTrack " << termcolor::reset << path << "\n"; } + } catch (...) { + tracks.pop_back(); } - ++paths; } if (good) { processed = false; @@ -1830,6 +1842,13 @@ namespace Manager { } } + void GwPlot::pathDrop(GLFWwindow* wind, int count, const char** paths) { + for (int i=0; i < count; ++ i) { + std::string pth = *paths; + addTrack(pth); + } + } + int GwPlot::getCollectionIdx(float x, float y) { if (y <= refSpace) { return REFERENCE_TRACK; //-2 diff --git a/src/plot_manager.h b/src/plot_manager.h index 0d38ac4..a8657dd 100644 --- a/src/plot_manager.h +++ b/src/plot_manager.h @@ -145,6 +145,14 @@ namespace Manager { void addBam(std::string &bam_path); + void removeBam(int index); + + void addTrack(std::string &path, bool print_message); + + void removeTrack(int index); + + void removeRegion(int index); + void addVariantTrack(std::string &path, int startIndex, bool cacheStdin, bool useFullPath); void addFilter(std::string &filter_str); From a4162f39eb0c9f8aa12375dadd51f887ad130655 Mon Sep 17 00:00:00 2001 From: kcleal Date: Sun, 3 Mar 2024 21:10:59 +0000 Subject: [PATCH 14/51] Added more functions --- src/plot_controls.cpp | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/src/plot_controls.cpp b/src/plot_controls.cpp index 434a3f0..149adca 100644 --- a/src/plot_controls.cpp +++ b/src/plot_controls.cpp @@ -521,7 +521,6 @@ namespace Manager { redraw = false; processed = true; return false; -// throw CloseException(); } else if (inputText == ":" || inputText == "/") { inputText = ""; return true; @@ -867,17 +866,6 @@ namespace Manager { inputText = ""; imageCache.clear(); return true; -// } else if (inputText == "low-mem") { -// opts.low_mem = !(opts.low_mem); -// redraw = false; -// if (mode == SINGLE) { -// processed = true; -// } else { -// processed = false; -// } -// inputText = ""; -// std::cout << "Low memory mode " << ((opts.low_mem) ? "on" : "off") << std::endl; -// return true; } else if (Utils::startsWith(inputText, "mate")) { std::string mate; Utils::parseMateLocation(selectedAlign, mate, target_qname); @@ -957,26 +945,6 @@ namespace Manager { opts.tlen_yscale = !opts.tlen_yscale; std::cerr << opts.max_tlen << std::endl; valid = true; -// samMaxY = opts.ylim; - //opts.max_tlen = samMaxY; -// } -// else if (Utils::startsWith(inputText, "tlen-y")) { -// valid = true; -// std::vector split = Utils::split(inputText, delim); -// if (split.size() == 2) { -// try { -// opts.max_tlen = std::stoi(split.back()); -// opts.tlen_yscale = true; -// } catch (...) { -// std::cerr << termcolor::red << "Error:" << termcolor::reset << " 'tlen-y NUMBER' not understood\n"; -// return true; -// } -// } else { -// opts.tlen_yscale = !(opts.tlen_yscale); -// if (!opts.tlen_yscale) { -// samMaxY = opts.ylim; -// } -// } } else if (Utils::startsWith(inputText, "goto")) { std::vector split = Utils::split(inputText, delim_q); if (split.size() == 1) { From 80f68cf26c578aa1c301fb37e3dfe1a6bb0e86ff Mon Sep 17 00:00:00 2001 From: kcleal Date: Tue, 5 Mar 2024 10:12:13 +0000 Subject: [PATCH 15/51] emvoed wget for curl --- Makefile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index c3e0b63..d749371 100644 --- a/Makefile +++ b/Makefile @@ -62,16 +62,16 @@ prep: @if [ "$(PLATFORM)" = "Darwin" ]; then \ SKIA_LINK=""; \ echo "Downloading pre-built skia for MacOS-Intel"; mkdir -p lib/skia; \ - cd lib/skia && wget -O skia.zip "https://github.com/JetBrains/skia-build/releases/download/m93-87e8842e8c/Skia-m93-87e8842e8c-macos-Release-x64.zip" && unzip -o skia.zip && rm skia.zip && cd ../../; \ + cd lib/skia && curl -L -o skia.zip "https://github.com/JetBrains/skia-build/releases/download/m93-87e8842e8c/Skia-m93-87e8842e8c-macos-Release-x64.zip" && unzip -o skia.zip && rm skia.zip && cd ../../; \ elif [ "$(PLATFORM)" = "Arm64" ]; then \ echo "Downloading pre-built skia for MacOS-Arm64"; mkdir -p lib/skia; \ - cd lib/skia && wget -O skia.tar.gz "https://github.com/kcleal/skia_build_arm64/releases/download/v0.0.1/skia.zip" && tar -xvf skia.tar.gz && rm skia.tar.gz && cd ../../; \ + cd lib/skia && curl -L -o skia.tar.gz "https://github.com/kcleal/skia_build_arm64/releases/download/v0.0.1/skia.zip" && tar -xvf skia.tar.gz && rm skia.tar.gz && cd ../../; \ elif [ "$(PLATFORM)" = "Linux" ] && [ "$(USE_GL)" = "1" ]; then \ echo "Downloading pre-built skia for Linux with GL"; mkdir -p lib/skia; \ - cd lib/skia && wget -O skia.zip "https://github.com/JetBrains/skia-build/releases/download/m93-87e8842e8c/Skia-m93-87e8842e8c-linux-Release-x64.zip" && unzip -o skia.zip && rm skia.zip && cd ../../; \ + cd lib/skia && curl -L -o skia.zip "https://github.com/JetBrains/skia-build/releases/download/m93-87e8842e8c/Skia-m93-87e8842e8c-linux-Release-x64.zip" && unzip -o skia.zip && rm skia.zip && cd ../../; \ elif [ "$(PLATFORM)" = "Linux" ]; then \ echo "Downloading pre-built skia for Linux with EGL"; mkdir -p lib/skia; \ - cd lib/skia && wget -O skia.tar.gz "https://github.com/kcleal/skia_build_arm64/releases/download/v0.0.1/skia-m93-linux-Release-x64.tar.gz" && tar -xvf skia.tar.gz && rm skia.tar.gz && cd ../../; \ + cd lib/skia && curl -L -o skia.tar.gz "https://github.com/kcleal/skia_build_arm64/releases/download/v0.0.1/skia-m93-linux-Release-x64.tar.gz" && tar -xvf skia.tar.gz && rm skia.tar.gz && cd ../../; \ fi From 48deed3a5720117a788d2959bc6c0b103b3b40ad Mon Sep 17 00:00:00 2001 From: kcleal Date: Tue, 5 Mar 2024 11:59:46 +0000 Subject: [PATCH 16/51] updated libbigwig --- lib/libBigWig/io.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/libBigWig/io.c b/lib/libBigWig/io.c index 96d205e..9b20b1a 100644 --- a/lib/libBigWig/io.c +++ b/lib/libBigWig/io.c @@ -14,7 +14,7 @@ size_t GLOBAL_DEFAULTBUFFERSIZE; #ifndef NOCURL uint64_t getContentLength(const URL_t *URL) { double size; - if(curl_easy_getinfo(URL->x.curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &size) != CURLE_OK) { + if(curl_easy_getinfo(URL->x.curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &size) != CURLE_OK) { return 0; } if(size== -1.0) return 0; From 868316802c82d0f72ecca5a217b3e5d21c98fee5 Mon Sep 17 00:00:00 2001 From: kcleal Date: Tue, 5 Mar 2024 12:40:54 +0000 Subject: [PATCH 17/51] updated shared --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index d749371..8267363 100644 --- a/Makefile +++ b/Makefile @@ -147,7 +147,7 @@ ifeq ($(UNAME_S),Darwin) endif shared: CXXFLAGS += -fPIC -DBUILDING_LIBGW - +shared: CFLAGS += -fPIC shared: $(OBJECTS) ifeq ($(UNAME_S),Darwin) From 24f1c79f4daf36bddce8fccc6f8f0f456b59ee5d Mon Sep 17 00:00:00 2001 From: kcleal Date: Mon, 29 Apr 2024 14:59:23 +0100 Subject: [PATCH 18/51] Added flask demo --- lib/libBigWig/io.c | 7 +++++++ src/plot_controls.cpp | 30 ++++++++++++++++-------------- src/plot_manager.cpp | 12 ++++++------ src/plot_manager.h | 12 ++++++------ 4 files changed, 35 insertions(+), 26 deletions(-) diff --git a/lib/libBigWig/io.c b/lib/libBigWig/io.c index 9b20b1a..0e2968d 100644 --- a/lib/libBigWig/io.c +++ b/lib/libBigWig/io.c @@ -14,9 +14,16 @@ size_t GLOBAL_DEFAULTBUFFERSIZE; #ifndef NOCURL uint64_t getContentLength(const URL_t *URL) { double size; + +#if LIBCURL_VERSION_NUM >= 0x073700 /* 7.55.0 */ + if(curl_easy_getinfo(URL->x.curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &size) != CURLE_OK) { + return 0; + } +#else if(curl_easy_getinfo(URL->x.curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &size) != CURLE_OK) { return 0; } +#endif if(size== -1.0) return 0; return (uint64_t) size; } diff --git a/src/plot_controls.cpp b/src/plot_controls.cpp index 149adca..1e01fed 100644 --- a/src/plot_controls.cpp +++ b/src/plot_controls.cpp @@ -1429,7 +1429,7 @@ namespace Manager { } } - void GwPlot::keyPress(GLFWwindow *wind, int key, int scancode, int action, int mods) { + void GwPlot::keyPress(int key, int scancode, int action, int mods) { // Decide if the key is part of a user input command (inputText) or a request to process a command / refresh screen // Of note, mouseButton events may be translated into keyPress events and processed here // For example, clicking on a commands from the menu pop-up will translate into a keyPress ENTER and @@ -1444,7 +1444,7 @@ namespace Manager { return; } } catch (CloseException & mce) { - glfwSetWindowShouldClose(wind, GLFW_TRUE); + glfwSetWindowShouldClose(window, GLFW_TRUE); } // key events concerning the menu are handled here if (mode != Show::SETTINGS && key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) { @@ -1810,7 +1810,7 @@ namespace Manager { } } - void GwPlot::pathDrop(GLFWwindow* wind, int count, const char** paths) { + void GwPlot::pathDrop(int count, const char** paths) { for (int i=0; i < count; ++ i) { std::string pth = *paths; addTrack(pth); @@ -1903,7 +1903,8 @@ namespace Manager { } } - void GwPlot::mouseButton(GLFWwindow* wind, int button, int action, int mods) { + void GwPlot::mouseButton(int button, int action, int mods) { + GLFWwindow* wind = window; double x, y; glfwGetCursorPos(window, &x, &y); @@ -1958,7 +1959,7 @@ namespace Manager { double yPos_fb = y; convertScreenCoordsToFrameBufferCoords(wind, &xPos_fb, &yPos_fb, fb_width, fb_height); if (xPos_fb > 50 && xPos_fb < 50 + fonts.overlayWidth * 20 && action == GLFW_RELEASE) { - keyPress(wind, GLFW_KEY_ENTER, 0, GLFW_PRESS, 0); + keyPress(GLFW_KEY_ENTER, 0, GLFW_PRESS, 0); return; } return; @@ -2362,7 +2363,8 @@ namespace Manager { } } - void GwPlot::mousePos(GLFWwindow* wind, double xPos, double yPos) { + void GwPlot::mousePos(double xPos, double yPos) { + GLFWwindow* wind = window; int windX, windY; glfwGetWindowSize(wind, &windX, &windY); if (yPos < 0 || xPos < 0 || xPos > windX || yPos > windY) { @@ -2638,31 +2640,31 @@ namespace Manager { } } - void GwPlot::scrollGesture(GLFWwindow* wind, double xoffset, double yoffset) { + void GwPlot::scrollGesture(double xoffset, double yoffset) { if (mode == Manager::SINGLE) { if (std::fabs(yoffset) > std::fabs(xoffset) && 0.1 < std::fabs(yoffset)) { if (yoffset < 0) { - keyPress(wind, opts.zoom_out, 0, GLFW_PRESS, 0); + keyPress(opts.zoom_out, 0, GLFW_PRESS, 0); } else { - keyPress(wind, opts.zoom_in, 0, GLFW_PRESS, 0); + keyPress(opts.zoom_in, 0, GLFW_PRESS, 0); } } else if (std::fabs(xoffset) > std::fabs(yoffset) && 0.1 < std::fabs(xoffset)) { if (xoffset < 0) { - keyPress(wind, opts.scroll_right, 0, GLFW_PRESS, 0); + keyPress(opts.scroll_right, 0, GLFW_PRESS, 0); } else { - keyPress(wind, opts.scroll_left, 0, GLFW_PRESS, 0); + keyPress(opts.scroll_left, 0, GLFW_PRESS, 0); } } } else if (std::fabs(yoffset) > std::fabs(xoffset) && 0.1 < std::fabs(yoffset)) { if (yoffset < 0) { - keyPress(wind, opts.scroll_right, 0, GLFW_PRESS, 0); + keyPress(opts.scroll_right, 0, GLFW_PRESS, 0); } else { - keyPress(wind, opts.scroll_left, 0, GLFW_PRESS, 0); + keyPress(opts.scroll_left, 0, GLFW_PRESS, 0); } } } - void GwPlot::windowResize(GLFWwindow* wind, int x, int y) { + void GwPlot::windowResize(int x, int y) { resizeTriggered = true; resizeTimer = std::chrono::high_resolution_clock::now(); glfwGetFramebufferSize(window, &fb_width, &fb_height); diff --git a/src/plot_manager.cpp b/src/plot_manager.cpp index 21a7f96..63c7db6 100644 --- a/src/plot_manager.cpp +++ b/src/plot_manager.cpp @@ -217,32 +217,32 @@ namespace Manager { glfwSetWindowUserPointer(window, this); auto func_key = [](GLFWwindow* w, int k, int s, int a, int m){ - static_cast(glfwGetWindowUserPointer(w))->keyPress(w, k, s, a, 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(w, c, 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(w, b, a, 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(w, x, 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(w, x, 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(w, x, y); + static_cast(glfwGetWindowUserPointer(w))->windowResize(x, y); }; glfwSetWindowSizeCallback(window, func_resize); diff --git a/src/plot_manager.h b/src/plot_manager.h index a8657dd..8c9d74e 100644 --- a/src/plot_manager.h +++ b/src/plot_manager.h @@ -177,17 +177,17 @@ namespace Manager { int startUI(GrDirectContext* sContext, SkSurface *sSurface, int delay); - void keyPress(GLFWwindow* window, int key, int scancode, int action, int mods); + void keyPress(int key, int scancode, int action, int mods); - void mouseButton(GLFWwindow* wind, int button, int action, int mods); + void mouseButton(int button, int action, int mods); - void mousePos(GLFWwindow* wind, double x, double y); + void mousePos(double x, double y); - void scrollGesture(GLFWwindow* wind, double xoffset, double yoffset); + void scrollGesture(double xoffset, double yoffset); - void windowResize(GLFWwindow* wind, int x, int y); + void windowResize(int x, int y); - void pathDrop(GLFWwindow* window, int count, const char** paths); + void pathDrop(int count, const char** paths); void runDraw(); From 08d8549f32ce553bb4dc5a375a41ef6324f9fcb5 Mon Sep 17 00:00:00 2001 From: kcleal Date: Thu, 9 May 2024 11:23:46 +0100 Subject: [PATCH 19/51] Added wasm build target --- .gitignore | 2 ++ Makefile | 68 +++++++++++++++++------------------- deps/compile_wasm_libs.sh | 49 ++++++++++++++++++++++++++ include/export_definitions.h | 25 +++++++++++-- lib/libBigWig/bigWigIO.h | 3 ++ lib/libBigWig/io.c | 3 ++ src/main.cpp | 3 ++ src/utils.h | 2 +- 8 files changed, 116 insertions(+), 39 deletions(-) create mode 100644 deps/compile_wasm_libs.sh diff --git a/.gitignore b/.gitignore index a6074bc..7215f59 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,5 @@ /lib/libgw/ libgw.so /libgw.dylib +/gw.wasm +/wasm_libs diff --git a/Makefile b/Makefile index 8267363..622dbbc 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,9 @@ UNAME_S := $(shell uname -s) ifeq ($(OS),Windows_NT) # assume we are using msys2-ucrt64 env PLATFORM = "Windows" else - ifeq ($(UNAME_S),Linux) + ifdef EMSCRIPTEN + PLATFORM = "Emscripten" + else ifeq ($(UNAME_S),Linux) PLATFORM = "Linux" else ifeq ($(UNAME_S),Darwin) ifeq ($(shell uname -m), arm64) @@ -23,17 +25,16 @@ else endif endif endif - -# try and add conda environment -ifdef CONDA_PREFIX - CPPFLAGS += -I$(CONDA_PREFIX)/include - LDFLAGS += -L$(CONDA_PREFIX)/lib -endif - -# Options to use target htslib or skia -ifdef HTSLIB - CPPFLAGS += -I$(HTSLIB) - LDFLAGS += -L$(HTSLIB) +ifneq ($(PLATFORM),"Emscripten") + # try and add conda environment + ifdef CONDA_PREFIX + CPPFLAGS += -I$(CONDA_PREFIX)/include + LDFLAGS += -L$(CONDA_PREFIX)/lib + endif + ifdef HTSLIB # Options to use target htslib or skia + CPPFLAGS += -I$(HTSLIB) + LDFLAGS += -L$(HTSLIB) + endif endif SKIA ?= "" @@ -42,19 +43,20 @@ ifneq ($(PLATFORM), "Windows") ifneq ($(SKIA),"") CPPFLAGS += -I$(SKIA) SKIA_PATH = $(wildcard $(SKIA)/out/Rel*) - else ifeq ($(PLATFORM),"Darwin") - CPPFLAGS += -I./lib/skia - SKIA_PATH = ./lib/skia/out/Release-x64 else ifeq ($(PLATFORM),"Arm64") CPPFLAGS += -I./lib/skia SKIA_PATH = ./lib/skia/out/Release-arm64 - else + else ifeq ($(PLATFORM),"Emscripten") + CPPFLAGS += -I./wasm_libs/skia -I./wasm_libs/htslib + SKIA_PATH = ./wasm_libs/skia/out/canvaskit_wasm + else # Darwin / Linux CPPFLAGS += -I./lib/skia SKIA_PATH = ./lib/skia/out/Release-x64 + LDFLAGS= endif endif -LDFLAGS += -L$(SKIA_PATH) +LDFLAGS += -L$(SKIA_PATH) SKIA_LINK="" USE_GL ?= "" # Else use EGL backend for Linux only @@ -74,15 +76,11 @@ prep: cd lib/skia && curl -L -o skia.tar.gz "https://github.com/kcleal/skia_build_arm64/releases/download/v0.0.1/skia-m93-linux-Release-x64.tar.gz" && tar -xvf skia.tar.gz && rm skia.tar.gz && cd ../../; \ fi - CXXFLAGS += -Wall -std=c++17 -fno-common -fwrapv -fno-omit-frame-pointer -O3 -DNDEBUG - LIBGW_INCLUDE= shared: LIBGW_INCLUDE=-I./libgw CPPFLAGS += -I./lib/libBigWig -I./include -I. $(LIBGW_INCLUDE) -I./src - -LDLIBS += -lskia -lm -ljpeg -lpng -lsvg -lhts -lfontconfig -lpthread - +LDLIBS += -lskia -lm -ljpeg -lpng -lpthread ifeq ($(PLATFORM),"Linux") # set platform flags and libs ifeq (${XDG_SESSION_TYPE},"wayland") # wayland is untested! LDLIBS += -lwayland-client @@ -93,33 +91,35 @@ ifeq ($(PLATFORM),"Linux") # set platform flags and libs CXXFLAGS += -D LINUX -D __STDC_FORMAT_MACROS LDFLAGS += -L/usr/local/lib # If installed from conda, glfw3 is named glfw, therefore if glfw3 is installed by another means use this: -# LDLIBS += -lGL -lfreetype -lfontconfig -luuid -lzlib -licu -ldl $(shell pkg-config --static --libs x11 xrandr xi xxf86vm glfw3) +# LDLIBS += -lGL -lfreetype -lfontconfig -luuid -lzlib -licu -ldl $(shell pkg-config --static --libs x11 xrandr xi xxf86vm glfw3) # LDLIBS += -lEGL -lGLESv2 -lfreetype -lfontconfig -luuid -lz -lcurl -licu -ldl -lglfw #$(shell pkg-config --static --libs x11 xrandr xi xxf86vm glfw3) -# LDLIBS += -lGL -lfreetype -lfontconfig -luuid -lz -lcurl -licu -ldl -lglfw ifeq ($(USE_GL),"1") LDLIBS += -lGL else LDLIBS += -lEGL -lGLESv2 endif - LDLIBS += -lfreetype -lfontconfig -luuid -lz -lcurl -licu -ldl -lglfw - + LDLIBS += -lhts -lfreetype -luuid -lz -lcurl -licu -ldl -lglfw -lsvg -lfontconfig else ifeq ($(PLATFORM),"Darwin") CPPFLAGS += -I/usr/local/include CXXFLAGS += -D OSX -stdlib=libc++ -arch x86_64 -fvisibility=hidden -mmacosx-version-min=11 -Wno-deprecated-declarations LDFLAGS += -undefined dynamic_lookup -framework OpenGL -framework AppKit -framework ApplicationServices -mmacosx-version-min=10.15 -L/usr/local/lib - LDLIBS += -lglfw -lzlib -lcurl -licu -ldl - + LDLIBS += -lhts -lglfw -lzlib -lcurl -licu -ldl -lsvg -lfontconfig else ifeq ($(PLATFORM),"Arm64") CPPFLAGS += -I/usr/local/include CXXFLAGS += -D OSX -stdlib=libc++ -arch arm64 -fvisibility=hidden -mmacosx-version-min=11 -Wno-deprecated-declarations LDFLAGS += -undefined dynamic_lookup -framework OpenGL -framework AppKit -framework ApplicationServices -mmacosx-version-min=10.15 -L/usr/local/lib - LDLIBS += -lglfw -lzlib -lcurl -licu -ldl - + LDLIBS += -lhts -lglfw -lzlib -lcurl -licu -ldl -lsvg -lfontconfig else ifeq ($(PLATFORM),"Windows") CXXFLAGS += -D WIN32 CPPFLAGS += $(shell pkgconf -cflags skia) $(shell ncursesw6-config --cflags) LDLIBS += $(shell pkgconf -libs skia) - LDLIBS += -lharfbuzz-subset -lglfw3 -lcurl + LDLIBS += -lhts -lharfbuzz-subset -lglfw3 -lcurl -lsvg -lfontconfig +else ifeq ($(PLATFORM),"Emscripten") + CPPFLAGS += -v --use-port=contrib.glfw3 -sUSE_ZLIB=1 -sUSE_FREETYPE=1 -sUSE_ICU=1 -I/usr/local/include + CFLAGS += -fPIC + CXXFLAGS += -DBUILDING_LIBGW -D__STDC_FORMAT_MACROS -fPIC + LDFLAGS += -v -L./wasm_libs/htslib -s RELOCATABLE=1 --no-entry -s STANDALONE_WASM + LDLIBS += -lwebgl.js -l:libhts.a endif OBJECTS = $(patsubst %.cpp, %.o, $(wildcard ./src/*.cpp)) @@ -128,14 +128,14 @@ OBJECTS += $(patsubst %.c, %.o, $(wildcard ./lib/libBigWig/*.c)) debug: CXXFLAGS += -g debug: LDFLAGS += -fsanitize=address -fsanitize=undefined -$(TARGET): $(OBJECTS) +$(TARGET): $(OBJECTS) # line 131 $(CXX) -g $(OBJECTS) $(LDFLAGS) $(LDLIBS) -o $@ clean: -rm -f *.o ./src/*.o ./src/*.o.tmp ./lib/libBigWig/*.o -rm -f $(TARGET) - -rm -rf libgw.* + -rm -rf libgw.* *.wasm @@ -160,5 +160,3 @@ endif -cp src/*.h lib/libgw/include -cp include/*.h* lib/libgw/include -mv $(SHARED_TARGET) lib/libgw/out - - diff --git a/deps/compile_wasm_libs.sh b/deps/compile_wasm_libs.sh new file mode 100644 index 0000000..4ff6b8a --- /dev/null +++ b/deps/compile_wasm_libs.sh @@ -0,0 +1,49 @@ +#!/bin/bash + +# Run this in the gw top-level directory (same as Makefile) +# Also dont forget to source emsdk environment +# source ~/Tools/emsdk/emsdk_env.sh + +emcc --version +mkdir -p wasm_libs +cd wasm_libs + +sudo apt-get install -y zlib1g-dev libbz2-dev libcurl4-gnutls-dev libssl-dev autoconf + + +# Compile LZMA to WebAssembly +LZMA_VERSION="5.2.5" +curl -LO "https://tukaani.org/xz/xz-${LZMA_VERSION}.tar.gz" +tar -xvf xz-${LZMA_VERSION}.tar.gz && rm xz*.gz +cd xz-${LZMA_VERSION} +emconfigure ./configure --disable-shared --disable-threads +emmake make -j4 CFLAGS="-Oz -fPIC -s USE_PTHREADS=0 -s EXPORT_ALL=1 -s ASSERTIONS=1" +cd .. + + +# Run ./configure +CFLAGS="-s USE_ZLIB=1 -s USE_BZIP2=1 ${CFLAGS_LZMA} -I./xz-${LZMA_VERSION}/src/liblzma/api/" +LDFLAGS="$LDFLAGS_LZMA -L./xz-${LZMA_VERSION}/src/liblzma/.libs/" +make clean +autoheader +autoconf +emconfigure ./configure CFLAGS="$CFLAGS -fPIC" LDFLAGS="$LDFLAGS --relocatable" + + +# Build htslib +curl -L -o htslib.tar.bz2 https://github.com/samtools/htslib/releases/download/1.20/htslib-1.20.tar.bz2 +tar -xvf htslib.tar.bz2 && rm htslib.tar.bz2 && mv htslib-1.20 htslib +cd htslib +emmake make -j4 CC=emcc AR=emar \ + CFLAGS="-O2 -fPIC $CFLAGS" \ + LDFLAGS="$EM_FLAGS -O2 -s ERROR_ON_UNDEFINED_SYMBOLS=0 $LDFLAGS --relocatable" + + +git clone https://github.com/google/skia.git +cd skia +VERSION=m93 +git checkout origin/chrome/${VERSION} +python3 tools/git-sync-deps +cd modules/canvaskit/ +sed -i 's/-s LLD_REPORT_UNDEFINED//g' compile.sh +make release -j4 diff --git a/include/export_definitions.h b/include/export_definitions.h index 4a83f5a..65be0a1 100644 --- a/include/export_definitions.h +++ b/include/export_definitions.h @@ -4,13 +4,32 @@ #pragma once +#ifdef __EMSCRIPTEN__ + #include +#endif + #if defined(__GNUC__) && __GNUC__ >= 4 - #if BUILDING_LIBGW - #define EXPORT __attribute__ ((visibility ("default"))) + #if defined(__EMSCRIPTEN__) + // Use EMSCRIPTEN_KEEPALIVE for functions only + #if defined(BUILDING_LIBGW) + #define EXPORT_FUNCTION EMSCRIPTEN_KEEPALIVE + #define EXPORT + #else + #define EXPORT_FUNCTION + #define EXPORT + #endif #else - #define EXPORT + // Non-Emscripten GCC or Clang + #if defined(BUILDING_LIBGW) + #define EXPORT_FUNCTION __attribute__((visibility("default"))) + #define EXPORT __attribute__((visibility("default"))) + #else + #define EXPORT_FUNCTION + #define EXPORT + #endif #endif #else #error "__GNUC__ not defined or version is less than 4" #endif + diff --git a/lib/libBigWig/bigWigIO.h b/lib/libBigWig/bigWigIO.h index 947bff6..abcb746 100644 --- a/lib/libBigWig/bigWigIO.h +++ b/lib/libBigWig/bigWigIO.h @@ -1,6 +1,9 @@ #ifndef LIBBIGWIG_IO_H #define LIBBIGWIG_IO_H +#ifdef __EMSCRIPTEN__ +#define NOCURL +#endif #ifndef NOCURL #include #else diff --git a/lib/libBigWig/io.c b/lib/libBigWig/io.c index 0e2968d..fa5d3a7 100644 --- a/lib/libBigWig/io.c +++ b/lib/libBigWig/io.c @@ -1,3 +1,6 @@ +#ifdef __EMSCRIPTEN__ +#define NOCURL +#endif #ifndef NOCURL #include #endif diff --git a/src/main.cpp b/src/main.cpp index 965f98f..ee41f1b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -40,6 +40,9 @@ #include "include/core/SkPicture.h" #include "include/svg/SkSVGCanvas.h" +#ifdef __EMSCRIPTEN__ + #include +#endif // skia context has to be managed from global space to work GrDirectContext *sContext = nullptr; diff --git a/src/utils.h b/src/utils.h index 6a2f6f3..da815e2 100644 --- a/src/utils.h +++ b/src/utils.h @@ -102,7 +102,7 @@ namespace Utils { int x, y; }; - EXPORT Dims parseDimensions(std::string &s); + EXPORT_FUNCTION Dims parseDimensions(std::string &s); int intervalOverlap(int start1, int end1, int start2, int end2); From 8e64fd365efb5245c9145b8023ea071f2b47c46a Mon Sep 17 00:00:00 2001 From: kcleal Date: Thu, 9 May 2024 11:40:56 +0100 Subject: [PATCH 20/51] Removed clang msys2 from actions --- .github/workflows/main.yml | 6 +++--- src/term_out.cpp | 6 +----- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0c5b16d..0fe60e0 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -12,7 +12,7 @@ jobs: strategy: fail-fast: false matrix: - sys: [clang64, ucrt64] + sys: [ucrt64] # clang64, name: ${{ matrix.sys }} runs-on: windows-latest defaults: @@ -140,9 +140,9 @@ jobs: echo "LIBDEFLATE BUILT" && pwd cd ${BUILD_DIR} - wget -O htslib.tar.bz2 https://github.com/samtools/htslib/releases/download/1.17/htslib-1.17.tar.bz2 + wget -O htslib.tar.bz2 https://github.com/samtools/htslib/releases/download/1.20/htslib-1.20.tar.bz2 tar -xvf htslib.tar.bz2 - mv htslib-1.17 htslib && rm htslib.tar.bz2 && cd htslib + mv htslib-1.20 htslib && rm htslib.tar.bz2 && cd htslib ./configure --enable-libcurl --enable-s3 --enable-lzma --enable-bz2 --with-libdeflate make -j3 echo "HTSLIB BUILT" && pwd diff --git a/src/term_out.cpp b/src/term_out.cpp index e4dc016..95dc40a 100644 --- a/src/term_out.cpp +++ b/src/term_out.cpp @@ -47,7 +47,6 @@ namespace Term { std::cout << termcolor::green << "insertions, ins " << termcolor::reset << "Toggle insertions\n"; std::cout << termcolor::green << "line " << termcolor::reset << "Toggle mouse position vertical line\n"; std::cout << termcolor::green << "link none/sv/all " << termcolor::reset << "Switch read-linking 'link all'\n"; - std::cout << termcolor::green << "low-mem " << termcolor::reset << "Toggle low-mem mode\n"; std::cout << termcolor::green << "log2-cov " << termcolor::reset << "Toggle scale coverage by log2\n"; std::cout << 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"; std::cout << termcolor::green << "mismatches, mm " << termcolor::reset << "Toggle mismatches\n"; @@ -55,7 +54,7 @@ namespace Term { std::cout << termcolor::green << "quit, q - " << termcolor::reset << "Quit GW\n"; std::cout << termcolor::green << "refresh, r - " << termcolor::reset << "Refresh and re-draw the window\n"; std::cout << 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"; - std::cout << termcolor::green << "sam " << termcolor::reset << "Print selected read in sam format'\n"; + std::cout << termcolor::green << "sam " << termcolor::reset << "Print selected read in sam format\n"; std::cout << termcolor::green << "settings " << termcolor::reset << "Open the settings menu'\n"; std::cout << 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"; std::cout << termcolor::green << "soft-clips, sc " << termcolor::reset << "Toggle soft-clips\n"; @@ -66,7 +65,6 @@ namespace Term { // std::cout << termcolor::green << " edges " << termcolor::reset << "\n"; // std::cout << termcolor::green << " insertions, ins " << termcolor::reset << "\n"; // std::cout << termcolor::green << " line " << termcolor::reset << "\n"; -// std::cout << termcolor::green << " low-mem " << termcolor::reset << "\n"; // std::cout << termcolor::green << " log2-cov " << termcolor::reset << "\n"; // std::cout << termcolor::green << " mismatches, mm " << termcolor::reset << "\n"; // std::cout << termcolor::green << " soft-clip, sc " << termcolor::reset << "\n"; @@ -144,8 +142,6 @@ namespace Term { std::cout << " Toggle line.\n A vertical line will turn on/off.\n\n"; } else if (s == "link" || s == "l") { std::cout << " 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 == "low-mem") { - std::cout << " Toggle low-mem mode.\n This will discard all base-quality information and sam tags from newly loaded alignments.\n\n"; } else if (s == "log2-cov") { std::cout << " Toggle log2-coverage.\n The coverage track will be scaled by log2.\n\n"; } else if (s == "mate") { From deb303f284461c4eb2d11c17047601159a415647 Mon Sep 17 00:00:00 2001 From: kcleal Date: Thu, 9 May 2024 13:50:24 +0100 Subject: [PATCH 21/51] Updated deb package --- .github/workflows/main.yml | 6 ++++-- Makefile | 6 +++--- src/term_out.cpp | 9 +++++++++ 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0fe60e0..d72619f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -114,7 +114,7 @@ jobs: run: | sudo apt update sudo apt-get install dh-make build-essential debhelper dh-virtualenv - sudo apt-get install devscripts fakeroot debootstrap pbuilder autotools-dev + sudo apt-get install debugedit devscripts fakeroot debootstrap pbuilder autotools-dev sudo apt install zlib1g-dev libbz2-dev liblzma-dev libncurses5-dev libncursesw5-dev libssl-dev sudo apt install libgl1-mesa-dev libfontconfig-dev libcurl4-openssl-dev libglfw3 libglfw3-dev - name: build @@ -144,6 +144,7 @@ jobs: tar -xvf htslib.tar.bz2 mv htslib-1.20 htslib && rm htslib.tar.bz2 && cd htslib ./configure --enable-libcurl --enable-s3 --enable-lzma --enable-bz2 --with-libdeflate + sed -i 's/-g//g' Makefile # strip debug information make -j3 echo "HTSLIB BUILT" && pwd cp -rf htslib ../include @@ -164,8 +165,9 @@ jobs: - name: package run: | - + LDLIBS += -lhts -L/usr/lib/gwhts dh_make --single --createorig --packagename gw_${version} --email clealk@cardiff.ac.uk --yes --native + printf "usr/bin/gw\nusr/share/icons/gw_icon.png\nusr/share/applications/gw.desktop\nusr/lib/gwhts/\nusr/lib/x86_64-linux-gnu/libglfw.so.3\nusr/lib/x86_64-linux-gnu/libglfw.so.3.3" > debian/install export LDFLAGS="$LDFLAGS -L./lib/skia/out/Release-x64 -L/usr/local/lib -L/usr/lib -L./usr/lib/gwhts -Wl,-rpath,'$ORIGIN/../lib/gwhts'" diff --git a/Makefile b/Makefile index 622dbbc..ec09315 100644 --- a/Makefile +++ b/Makefile @@ -76,7 +76,7 @@ prep: cd lib/skia && curl -L -o skia.tar.gz "https://github.com/kcleal/skia_build_arm64/releases/download/v0.0.1/skia-m93-linux-Release-x64.tar.gz" && tar -xvf skia.tar.gz && rm skia.tar.gz && cd ../../; \ fi -CXXFLAGS += -Wall -std=c++17 -fno-common -fwrapv -fno-omit-frame-pointer -O3 -DNDEBUG +CXXFLAGS += -Wall -std=c++17 -fno-common -fwrapv -fno-omit-frame-pointer -O3 -DNDEBUG -g LIBGW_INCLUDE= shared: LIBGW_INCLUDE=-I./libgw CPPFLAGS += -I./lib/libBigWig -I./include -I. $(LIBGW_INCLUDE) -I./src @@ -125,11 +125,11 @@ endif OBJECTS = $(patsubst %.cpp, %.o, $(wildcard ./src/*.cpp)) OBJECTS += $(patsubst %.c, %.o, $(wildcard ./lib/libBigWig/*.c)) -debug: CXXFLAGS += -g + debug: LDFLAGS += -fsanitize=address -fsanitize=undefined $(TARGET): $(OBJECTS) # line 131 - $(CXX) -g $(OBJECTS) $(LDFLAGS) $(LDLIBS) -o $@ + $(CXX) $(CXXFLAGS) $(OBJECTS) $(LDFLAGS) $(LDLIBS) -o $@ clean: diff --git a/src/term_out.cpp b/src/term_out.cpp index 95dc40a..db569f6 100644 --- a/src/term_out.cpp +++ b/src/term_out.cpp @@ -156,6 +156,15 @@ namespace Term { std::cout << " Remove a region, bam or track.\n Remove a region, bam or track by index. To remove a bam or track add a 'bam' or 'track' prefix.\n Examples:\n 'rm 0', 'rm bam1', 'rm track2'\n\n"; } else if (s == "sam") { std::cout << " Print the sam format of the read.\n First select a read using the mouse then type ':sam'.\n\n"; + } else if (s == "save") { + std::cout << " Save reads, snapshot or session to file.\n" + " The filepath extension you use will determine the output file type.\n" + " Examples:\n" + " 'save reads.bam' # Save all visible reads to reads.bam file. Any filters are applied.\n" + " 'save reads.cram' # Reads saved in cram format, the loaded reference genome to configure\n" + " 'save reads.sam' # Reads saved in sam format (human readable)\n" + " 'save view.png' # The current view is saved to view.png. Same functionality as 'snapshot' command\n" + " 'save session.xml' # The current session will be saved, allowing this session to be revisited\n\n"; } else if (s == "snapshot" || s == "s") { std::cout << " Save an image of the screen.\n" " Saves current window. If no name is provided, the image name will be 'chrom_start_end.png', \n" From a99becebcf40e08628ded721af72845eda34f464 Mon Sep 17 00:00:00 2001 From: Kez Cleal <42997789+kcleal@users.noreply.github.com> Date: Thu, 9 May 2024 14:20:49 +0100 Subject: [PATCH 22/51] Update main.yml --- .github/workflows/main.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d72619f..239396a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -165,7 +165,6 @@ jobs: - name: package run: | - LDLIBS += -lhts -L/usr/lib/gwhts dh_make --single --createorig --packagename gw_${version} --email clealk@cardiff.ac.uk --yes --native printf "usr/bin/gw\nusr/share/icons/gw_icon.png\nusr/share/applications/gw.desktop\nusr/lib/gwhts/\nusr/lib/x86_64-linux-gnu/libglfw.so.3\nusr/lib/x86_64-linux-gnu/libglfw.so.3.3" > debian/install From ebb088e7dc9a6d6bcfa6f799fc168fa6a1e8ba9a Mon Sep 17 00:00:00 2001 From: kcleal Date: Thu, 9 May 2024 16:31:45 +0100 Subject: [PATCH 23/51] Updated deb package --- .github/workflows/main.yml | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 239396a..5084256 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -143,8 +143,8 @@ jobs: wget -O htslib.tar.bz2 https://github.com/samtools/htslib/releases/download/1.20/htslib-1.20.tar.bz2 tar -xvf htslib.tar.bz2 mv htslib-1.20 htslib && rm htslib.tar.bz2 && cd htslib + sed -i 's/ -g -Wall/ -Wall/' Makefile # strip debug information ./configure --enable-libcurl --enable-s3 --enable-lzma --enable-bz2 --with-libdeflate - sed -i 's/-g//g' Makefile # strip debug information make -j3 echo "HTSLIB BUILT" && pwd cp -rf htslib ../include @@ -170,16 +170,19 @@ jobs: printf "usr/bin/gw\nusr/share/icons/gw_icon.png\nusr/share/applications/gw.desktop\nusr/lib/gwhts/\nusr/lib/x86_64-linux-gnu/libglfw.so.3\nusr/lib/x86_64-linux-gnu/libglfw.so.3.3" > debian/install export LDFLAGS="$LDFLAGS -L./lib/skia/out/Release-x64 -L/usr/local/lib -L/usr/lib -L./usr/lib/gwhts -Wl,-rpath,'$ORIGIN/../lib/gwhts'" + sed -i '/^Section/c\Section: Bioinformatics' debian/control sed -i '/^Homepage/c\Homepage: https://github.com/kcleal/gw' debian/control - sed -i '/^Description/c\Description: Genome browser and variant annotation' debian/control sed -i '/> debian/control - printf "Conflicts: libglfw3 (<< 3.3)\n" >> debian/control - printf "Replaces: libglfw3 \n" >> debian/control + # Override dh_shlibdeps to include /usr/lib/gwhts + printf '#!/usr/bin/make -f\n%:\ndh $@\noverride_dh_shlibdeps:\ndh_shlibdeps -l/usr/lib/gwhts\n' > debian/rules + chmod +x debian/rules - dpkg-buildpackage + dpkg-buildpackage -us -uc + - name: upload uses: actions/upload-artifact@v3 with: From dbf2725c05be1875a3deec0c58fcba80440d4e6d Mon Sep 17 00:00:00 2001 From: Kez Cleal <42997789+kcleal@users.noreply.github.com> Date: Thu, 9 May 2024 17:00:37 +0100 Subject: [PATCH 24/51] Update main.yml --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5084256..9e20864 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -178,7 +178,7 @@ jobs: printf "Conflicts: libglfw3 (<< 3.3)\nReplaces: libglfw3\n" >> debian/control # Override dh_shlibdeps to include /usr/lib/gwhts - printf '#!/usr/bin/make -f\n%:\ndh $@\noverride_dh_shlibdeps:\ndh_shlibdeps -l/usr/lib/gwhts\n' > debian/rules + printf '#!/usr/bin/make -f\n%%:\ndh $$@\noverride_dh_shlibdeps:\ndh_shlibdeps -l/usr/lib/gwhts\n' > debian/rules chmod +x debian/rules dpkg-buildpackage -us -uc From af8e9c8001c04b8f1b4de1cdb749a87c389d1885 Mon Sep 17 00:00:00 2001 From: kcleal Date: Fri, 10 May 2024 10:16:42 +0100 Subject: [PATCH 25/51] Updated deb package --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9e20864..928d367 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -178,7 +178,7 @@ jobs: printf "Conflicts: libglfw3 (<< 3.3)\nReplaces: libglfw3\n" >> debian/control # Override dh_shlibdeps to include /usr/lib/gwhts - printf '#!/usr/bin/make -f\n%%:\ndh $$@\noverride_dh_shlibdeps:\ndh_shlibdeps -l/usr/lib/gwhts\n' > debian/rules + printf '#!/usr/bin/make -f\n%%:\n\tdh $@\n\toverride_dh_shlibdeps:\n\tdh_shlibdeps -l/usr/lib/gwhts\n' > debian/rules chmod +x debian/rules dpkg-buildpackage -us -uc From 43bef1fb08461ba46d1d2e4930bf787e278c1646 Mon Sep 17 00:00:00 2001 From: kcleal Date: Fri, 10 May 2024 13:47:21 +0100 Subject: [PATCH 26/51] Work on making command interface better --- .github/workflows/main.yml | 2 +- src/plot_commands.cpp | 61 ++++++++++++++++++++++++++++++++++++++ src/plot_commands.h | 14 +++++++++ src/plot_controls.cpp | 10 +++++-- src/plot_manager.h | 4 +-- 5 files changed, 85 insertions(+), 6 deletions(-) create mode 100644 src/plot_commands.cpp create mode 100644 src/plot_commands.h diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 928d367..83d62ef 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -178,7 +178,7 @@ jobs: printf "Conflicts: libglfw3 (<< 3.3)\nReplaces: libglfw3\n" >> debian/control # Override dh_shlibdeps to include /usr/lib/gwhts - printf '#!/usr/bin/make -f\n%%:\n\tdh $@\n\toverride_dh_shlibdeps:\n\tdh_shlibdeps -l/usr/lib/gwhts\n' > debian/rules + printf '#!/usr/bin/make -f\n%%:\n\tdh $@\n\nclean:\n\tdh_clean\n\noverride_dh_shlibdeps:\n\tdh_shlibdeps -l/usr/lib/gwhts\n' > debian/rules chmod +x debian/rules dpkg-buildpackage -us -uc diff --git a/src/plot_commands.cpp b/src/plot_commands.cpp new file mode 100644 index 0000000..accd3d5 --- /dev/null +++ b/src/plot_commands.cpp @@ -0,0 +1,61 @@ +// +// Created by kez on 10/05/24. +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "hts_funcs.h" +#include "parser.h" +#include "plot_manager.h" +#include "themes.h" + + +namespace Commands { + + using Plot = Manager::GwPlot*; + + + bool triggerClose(Plot p) { + p->triggerClose = true; + p->redraw = false; + p->processed = true; + return false; + } + + // Command functions can access these parameters only + #define PARAMS [](Commands::Plot p, std::string& command, std::vector& parts) -> bool + + // Note the function map will be cached after first call. plt is bound, but parts are updated with each call + bool run_command_map(Plot p, std::string& command) { + + std::vector parts = Utils::split(command, ' '); + + static std::unordered_map&)>> functionMap = { + + {"q", PARAMS { return triggerClose(p); }}, + {"quit", PARAMS { return triggerClose(p); }} + + }; + + auto it = functionMap.find(parts[0]); + if (it != functionMap.end()) { + std::cout << "Command success!\n"; + return it->second(p, command, parts); // Execute the mapped function + } else { + std::cout << "Command not found!\n"; + return false; + } + } + + +} \ No newline at end of file diff --git a/src/plot_commands.h b/src/plot_commands.h new file mode 100644 index 0000000..c122d46 --- /dev/null +++ b/src/plot_commands.h @@ -0,0 +1,14 @@ +// +// Created by kez on 10/05/24. +// + +#pragma once + +#include +#include "plot_manager.h" + +namespace Commands { + + bool run_command_map(Manager::GwPlot* p, std::string& command); + +} diff --git a/src/plot_controls.cpp b/src/plot_controls.cpp index 1e01fed..90c5aef 100644 --- a/src/plot_controls.cpp +++ b/src/plot_controls.cpp @@ -17,6 +17,7 @@ #include "hts_funcs.h" #include "parser.h" #include "plot_manager.h" +#include "plot_commands.h" #include "menu.h" #include "segments.h" #include "termcolor.h" @@ -516,6 +517,11 @@ namespace Manager { } processText = false; // all command text will be processed below + bool success = Commands::run_command_map(this, inputText); + if (!success) { + return false; + } + if (inputText == "q" || inputText == "quit") { triggerClose = true; redraw = false; @@ -567,8 +573,7 @@ namespace Manager { processed = true; inputText = ""; return true; - } - else if (inputText == "line") { + } else if (inputText == "line") { drawLine = !drawLine; redraw = false; processed = true; @@ -605,7 +610,6 @@ namespace Manager { } imageCache.clear(); valid = true; - } else if (inputText =="sam") { valid = true; if (!selectedAlign.empty()) { diff --git a/src/plot_manager.h b/src/plot_manager.h index 8c9d74e..39455c2 100644 --- a/src/plot_manager.h +++ b/src/plot_manager.h @@ -81,7 +81,7 @@ namespace Manager { int samMaxY; int regionSelection, variantFileSelection; bool drawToBackWindow; - + bool triggerClose; bool redraw; bool processed; @@ -210,7 +210,7 @@ namespace Manager { bool resizeTriggered; bool regionSelectionTriggered; bool textFromSettings; - bool triggerClose; + std::chrono::high_resolution_clock::time_point resizeTimer, regionTimer; std::string target_qname; From 73014be192810088b56ebb0e82f6f40583c1aaa8 Mon Sep 17 00:00:00 2001 From: kcleal Date: Fri, 10 May 2024 16:35:06 +0100 Subject: [PATCH 27/51] More work on command interface --- .gitignore | 1 + src/plot_commands.cpp | 203 +++++++++++++++++++++++++++++++++++++++++- src/plot_controls.cpp | 172 +---------------------------------- src/plot_manager.cpp | 13 +-- src/plot_manager.h | 13 +-- src/utils.cpp | 5 +- 6 files changed, 221 insertions(+), 186 deletions(-) diff --git a/.gitignore b/.gitignore index 7215f59..1b8179d 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,4 @@ libgw.so /libgw.dylib /gw.wasm /wasm_libs +/src/plot_commands.o diff --git a/src/plot_commands.cpp b/src/plot_commands.cpp index accd3d5..ddcfb94 100644 --- a/src/plot_commands.cpp +++ b/src/plot_commands.cpp @@ -17,6 +17,7 @@ #include "hts_funcs.h" #include "parser.h" #include "plot_manager.h" +#include "term_out.h" #include "themes.h" @@ -24,6 +25,11 @@ namespace Commands { using Plot = Manager::GwPlot*; + bool noOp(Plot p) { + p->redraw = false; + p->processed = true; + return false; + } bool triggerClose(Plot p) { p->triggerClose = true; @@ -32,6 +38,180 @@ namespace Commands { return false; } + bool getHelp(Plot p, std::string& command, std::vector& parts) { + if (command == "help" || command == "man") { + Term::help(p->opts); + } else if (parts.size() == 2) { + Term::manuals(parts[1]); + } + p->redraw = false; + p->processed = true; + return false; + } + + bool refreshGw(Plot p) { + p->redraw = true; + p->processed = false; + p->imageCache.clear(); + p->filters.clear(); + p->target_qname = ""; + for (auto &cl: p->collections) { cl.vScroll = 0; } + return false; + } + + bool line(Plot p) { + p->drawLine = !p->drawLine; + p->redraw = true; + p->processed = true; + return false; + } + + bool settings(Plot p) { + p->last_mode = p->mode; + p->mode = Manager::Show::SETTINGS; + p->redraw = true; + p->processed = true; + return false; + } + + bool sam(Plot p) { + if (!p->selectedAlign.empty()) { + Term::printSelectedSam(p->selectedAlign); + } + p->redraw = false; + p->processed = true; + return false; + } + + bool link(Plot p, std::string& command, std::vector& parts) { + bool relink = false; + if (command == "link" || command == "link all") { + relink = (p->opts.link_op != 2) ? true : false; + p->opts.link_op = 2; + } else if (parts.size() == 2) { + if (parts[1] == "sv") { + relink = (p->opts.link_op != 1) ? true : false; + p->opts.link_op = 1; + } else if (parts[1] == "none") { + relink = (p->opts.link_op != 0) ? true : false; + p->opts.link_op = 0; + } + } + if (relink) { + p->imageCache.clear(); + HGW::refreshLinked(p->collections, p->opts, &p->samMaxY); + p->redraw = true; + p->processed = true; + } + return false; + } + + bool count(Plot p, std::string& command) { + std::string str = command; + str.erase(0, 6); + Parse::countExpression(p->collections, str, p->headers, p->bam_paths, (int)p->bams.size(), (int)p->regions.size()); + p->redraw = false; + p->processed = true; + return false; + } + + bool addFilter(Plot p, std::string& command) { + std::string str = command; + str.erase(0, 7); + if (str.empty()) { + return false; + } + for (auto &s: Utils::split(str, ';')) { + Parse::Parser ps = Parse::Parser(); + int rr = ps.set_filter(s, (int)p->bams.size(), (int)p->regions.size()); + if (rr > 0) { + p->filters.push_back(ps); + std::cout << command << std::endl; + } + } + p->imageCache.clear(); + p->redraw = true; + p->processed = false; + return false; + } + + bool tags(Plot p, std::string& command) { + if (!p->selectedAlign.empty()) { + std::string str = command; + str.erase(0, 4); + std::vector splitTags = Utils::split(str, ' '); + std::vector splitA = Utils::split(p->selectedAlign, '\t'); + if (splitA.size() > 11) { + Term::clearLine(); + std::cout << "\r"; + int i = 0; + for (auto &s : splitA) { + if (i > 11) { + std::string t = s.substr(0, s.find(':')); + if (splitTags.empty()) { + std::string rest = s.substr(s.find(':'), s.size()); + std::cout << termcolor::green << t << termcolor::reset << rest << "\t"; + } else { + for (auto &target : splitTags) { + if (target == t) { + std::string rest = s.substr(s.find(':'), s.size()); + std::cout << termcolor::green << t << termcolor::reset << rest << "\t"; + } + } + } + } + i += 1; + } + std::cout << std::endl; + } + } + p->redraw = false; + p->processed = true; + return false; + } + + bool findRead(Plot p, std::vector parts) { + if (!p->target_qname.empty() && parts.size() == 1) { + return false; + } else if (parts.size() == 2) { + p->target_qname = parts.back(); + } else { + std::cerr << termcolor::red << "Error:" << termcolor::reset << " please provide one qname\n"; + return false; + } + p->highlightQname(); + p->redraw = true; + p->processed = true; + p->imageCache.clear(); + return false; + } + + bool setYlim(Plot p, std::vector parts) { + int ylim = p->opts.ylim; + int samMaxY = p->opts.ylim; + int max_tlen = p->opts.max_tlen; + try { + if (!p->opts.tlen_yscale) { + ylim = std::stoi(parts.back()); + samMaxY = p->opts.ylim; + } else { + max_tlen = std::stoi(parts.back()); + samMaxY = p->opts.max_tlen; + } + } catch (...) { + std::cerr << termcolor::red << "Error:" << termcolor::reset << " ylim invalid value\n"; + return false; + } + p->imageCache.clear(); + HGW::refreshLinked(p->collections, p->opts, &p->samMaxY); + p->processed = true; + p->redraw = true; + p->opts.ylim = ylim; + p->samMaxY = samMaxY; + p->opts.max_tlen = max_tlen; + return false; + } + // Command functions can access these parameters only #define PARAMS [](Commands::Plot p, std::string& command, std::vector& parts) -> bool @@ -42,8 +222,25 @@ namespace Commands { static std::unordered_map&)>> functionMap = { - {"q", PARAMS { return triggerClose(p); }}, - {"quit", PARAMS { return triggerClose(p); }} + {":", PARAMS { return noOp(p); }}, + {"/", PARAMS { return noOp(p); }}, + {"q", PARAMS { return triggerClose(p); }}, + {"quit", PARAMS { return triggerClose(p); }}, + {"r", PARAMS { return refreshGw(p); }}, + {"refresh", PARAMS { return refreshGw(p); }}, + {"line", PARAMS { return line(p); }}, + {"settings", PARAMS { return settings(p); }}, + {"sam", PARAMS { return sam(p); }}, + {"help", PARAMS { return getHelp(p, command, parts); }}, + {"man", PARAMS { return getHelp(p, command, parts); }}, + {"link", PARAMS { return link(p, command, parts); }}, + {"count", PARAMS { return count(p, command); }}, + {"filter", PARAMS { return addFilter(p, command); }}, + {"tags", PARAMS { return tags(p, command); }}, + {"f", PARAMS { return findRead(p, parts); }}, + {"find", PARAMS { return findRead(p, parts); }}, + {"ylim", PARAMS { return setYlim(p, parts); }}, + }; @@ -55,6 +252,8 @@ namespace Commands { std::cout << "Command not found!\n"; return false; } + + p->inputText = ""; } diff --git a/src/plot_controls.cpp b/src/plot_controls.cpp index 90c5aef..cba3564 100644 --- a/src/plot_controls.cpp +++ b/src/plot_controls.cpp @@ -522,177 +522,7 @@ namespace Manager { return false; } - if (inputText == "q" || inputText == "quit") { - triggerClose = true; - redraw = false; - processed = true; - return false; - } else if (inputText == ":" || inputText == "/") { - inputText = ""; - return true; - } else if (inputText == "help" || inputText == "h") { - Term::help(opts); - redraw = false; - processed = true; - inputText = ""; - return true; - } else if (Utils::startsWith(inputText, "man ")) { - inputText.erase(0, 4); - Term::manuals(inputText); - redraw = false; - processed = true; - inputText = ""; - return true; - } else if (inputText == "refresh" || inputText == "r") { - valid = true; - imageCache.clear(); - filters.clear(); - target_qname = ""; - for (auto &cl: collections) { cl.vScroll = 0; } - } else if (inputText == "link" || inputText == "link all") { - opts.link_op = 2; - imageCache.clear(); - HGW::refreshLinked(collections, opts, &samMaxY); - redraw = true; - processed = true; - inputText = ""; - return true; - } else if (inputText == "link sv") { - opts.link_op = 1; - imageCache.clear(); - HGW::refreshLinked(collections, opts, &samMaxY); - redraw = true; - processed = true; - inputText = ""; - return true; - } else if (inputText == "link none") { - opts.link_op = 0; - imageCache.clear(); - HGW::refreshLinked(collections, opts, &samMaxY); - redraw = true; - processed = true; - inputText = ""; - return true; - } else if (inputText == "line") { - drawLine = !drawLine; - redraw = false; - processed = true; - inputText = ""; - return true; - } else if (Utils::startsWith(inputText, "count")) { - std::string str = inputText; - str.erase(0, 6); - Parse::countExpression(collections, str, headers, bam_paths, (int)bams.size(), (int)regions.size()); - redraw = false; - processed = true; - inputText = ""; - return true; - } else if (inputText == "settings" ) { - last_mode = mode; - mode = Show::SETTINGS; - redraw = true; - processed = true; - inputText = ""; - return true; - } else if (Utils::startsWith(inputText, "filter ")) { - std::string str = inputText; - str.erase(0, 7); - for (auto &s: Utils::split(str, ';')) { - Parse::Parser p = Parse::Parser(); - int rr = p.set_filter(s, (int)bams.size(), (int)regions.size()); - if (rr > 0) { - filters.push_back(p); - std::cout << inputText << std::endl; - } else { - inputText = ""; - return false; - } - } - imageCache.clear(); - valid = true; - } else if (inputText =="sam") { - valid = true; - if (!selectedAlign.empty()) { - Term::printSelectedSam(selectedAlign); - } - redraw = false; - processed = true; - inputText = ""; - return true; - } else if (Utils::startsWith(inputText, "tags")) { - valid = true; - if (!selectedAlign.empty()) { - std::string str = inputText; - str.erase(0, 4); - std::vector splitTags = Utils::split(str, delim); - - std::vector split = Utils::split(selectedAlign, '\t'); - if (split.size() > 11) { - Term::clearLine(); - std::cout << "\r"; - int i = 0; - for (auto &s : split) { - if (i > 11) { - std::string t = s.substr(0, s.find(':')); - if (splitTags.empty()) { - std::string rest = s.substr(s.find(':'), s.size()); - std::cout << termcolor::green << t << termcolor::reset << rest << "\t"; - } else { - for (auto &target : splitTags) { - if (target == t) { - std::string rest = s.substr(s.find(':'), s.size()); - std::cout << termcolor::green << t << termcolor::reset << rest << "\t"; - } - } - } - } - i += 1; - } - std::cout << std::endl; - } - } - redraw = false; - processed = true; - inputText = ""; - return true; - } else if (Utils::startsWith(inputText, "f ") || Utils::startsWith(inputText, "find ")) { - std::vector split = Utils::split(inputText, delim); - if (!target_qname.empty() && split.size() == 1) { - } else if (split.size() == 2) { - target_qname = split.back(); - } else { - std::cerr << termcolor::red << "Error:" << termcolor::reset << " please provide one qname\n"; - inputText = ""; - return true; - } - highlightQname(); - redraw = true; - processed = true; - inputText = ""; - imageCache.clear(); - return true; - - } else if (Utils::startsWith(inputText, "ylim")) { - std::vector split = Utils::split(inputText, delim); - try { - if (!opts.tlen_yscale) { - opts.ylim = std::stoi(split.back()); - samMaxY = opts.ylim; - } else { - opts.max_tlen = std::stoi(split.back()); - samMaxY = opts.max_tlen; - } - - imageCache.clear(); - HGW::refreshLinked(collections, opts, &samMaxY); - processed = true; - redraw = true; - } catch (...) { - std::cerr << termcolor::red << "Error:" << termcolor::reset << " ylim invalid value\n"; - } - inputText = ""; - return true; - } else if (Utils::startsWith(inputText, "indel-length")) { + if (Utils::startsWith(inputText, "indel-length")) { std::vector split = Utils::split(inputText, delim); try { opts.indel_length = std::stoi(split.back()); diff --git a/src/plot_manager.cpp b/src/plot_manager.cpp index 63c7db6..63c8209 100644 --- a/src/plot_manager.cpp +++ b/src/plot_manager.cpp @@ -420,7 +420,7 @@ namespace Manager { } int GwPlot::startUI(GrDirectContext* sContext, SkSurface *sSurface, int delay) { - std::cerr << "Type ':help' or ':h' for more info\n"; + std::cerr << "Type ':help' for more info\n"; vCursor = glfwCreateStandardCursor(GLFW_VRESIZE_CURSOR); setGlfwFrameBufferSize(); @@ -665,12 +665,10 @@ namespace Manager { if (nbams > 0) { trackY = (fbh - totalCovY - totalTabixY - refSpace - sliderSpace) / nbams; - yScaling = ( (fbh - totalCovY - totalTabixY - refSpace - sliderSpace - (gap * nbams)) / (float)samMaxY) / nbams; + yScaling = ( (fbh - totalCovY - totalTabixY - refSpace - sliderSpace - (gap * nbams)) / (double)samMaxY) / nbams; if (yScaling > 3 * monitorScale) { yScaling = (int)yScaling; } - // try to scale to pixel boundary -// yScaling = (samMaxY < 80) ? (float)(int)yScaling : yScaling; } else { trackY = 0; yScaling = 0; @@ -695,7 +693,12 @@ namespace Manager { pointSlop = (tan(0.42) * (yScaling * 0.5)); // radians textDrop = std::fmax(0, (yScaling - fonts.fontHeight) * 0.5); - pH = yScaling * 0.85; // polygonHeight + if (yScaling > 3) { + pH = yScaling * 0.85; // polygonHeight + } else { + pH = yScaling; + } + if (opts.tlen_yscale) { pH = trackY / (float) opts.ylim; yScaling *= 0.95; diff --git a/src/plot_manager.h b/src/plot_manager.h index 39455c2..4d27648 100644 --- a/src/plot_manager.h +++ b/src/plot_manager.h @@ -84,6 +84,7 @@ namespace Manager { bool triggerClose; bool redraw; bool processed; + bool drawLine; std::vector pixelMemory; @@ -91,6 +92,8 @@ namespace Manager { std::string inputText; + std::string target_qname; + std::vector bam_paths; std::vector bams; std::vector headers; @@ -203,17 +206,17 @@ namespace Manager { bool commandProcessed(); + void highlightQname(); + private: long frameId; - bool drawLine; bool resizeTriggered; bool regionSelectionTriggered; bool textFromSettings; std::chrono::high_resolution_clock::time_point resizeTimer, regionTimer; - std::string target_qname; std::string cursorGenomePos; int target_pos; @@ -226,11 +229,11 @@ namespace Manager { float totalCovY, covY, totalTabixY, tabixY, trackY, regionWidth, bamHeight, refSpace, sliderSpace; - float pointSlop, textDrop, pH; + double pointSlop, textDrop, pH; double xDrag, xOri, lastX, yDrag, yOri, lastY; - float yScaling; + double yScaling; // std::vector> extraPixelArrays; // one for each thread @@ -265,8 +268,6 @@ namespace Manager { int getCollectionIdx(float x, float y); - void highlightQname(); - void updateCursorGenomePos(float xOffset, float xScaling, float xPos, Utils::Region *region, int bamIdx); void updateSlider(float xPos); diff --git a/src/utils.cpp b/src/utils.cpp index 850af1a..4306289 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -186,8 +186,9 @@ namespace Utils { Utils::strToRegion(®, s, ','); } else if (s.find("\t") != std::string::npos) { Utils::strToRegion(®, s, '\t'); - } else if (s.find("_") != std::string::npos) { - Utils::strToRegion(®, s, '_'); +// } +// else if (s.find("_") != std::string::npos) { +// Utils::strToRegion(®, s, '_'); } else if (s.find(" ") != std::string::npos) { Utils::strToRegion(®, s, ' '); } else { From c89d363b6ead5b5a11a39d7e0cd6e67f392edbe7 Mon Sep 17 00:00:00 2001 From: kcleal Date: Fri, 10 May 2024 20:49:00 +0100 Subject: [PATCH 28/51] More work on command interface --- src/plot_commands.cpp | 155 ++++++++++++++++++++++++++++++++++++++++++ src/plot_controls.cpp | 153 +---------------------------------------- 2 files changed, 156 insertions(+), 152 deletions(-) diff --git a/src/plot_commands.cpp b/src/plot_commands.cpp index ddcfb94..e58c04a 100644 --- a/src/plot_commands.cpp +++ b/src/plot_commands.cpp @@ -83,6 +83,62 @@ namespace Commands { return false; } + bool insertions(Plot p) { + p->opts.small_indel_threshold = (p->opts.small_indel_threshold == 0) ? std::stoi(p->opts.myIni["view_thresholds"]["small_indel"]) : 0; + if (p->mode == Manager::Show::SINGLE) { + p->processed = true; + } else { + p->processed = false; + } + p->redraw = true; + p->imageCache.clear(); + return false; + } + + bool mismatches(Plot p) { + p->opts.snp_threshold = (p->opts.snp_threshold == 0) ? std::stoi(p->opts.myIni["view_thresholds"]["snp"]) : 0; + if (p->mode == Manager::Show::SINGLE) { + p->processed = true; + for (auto &cl : p->collections) { + cl.skipDrawingReads = false; + cl.skipDrawingCoverage = false; + } + } else { + p->processed = false; + } + p->redraw = true; + p->imageCache.clear(); + return true; + } + + bool edges(Plot p) { + p->opts.edge_highlights = (p->opts.edge_highlights == 0) ? std::stoi(p->opts.myIni["view_thresholds"]["edge_highlights"]) : 0; + if (p->mode == Manager::Show::SINGLE) { + p->processed = true; + for (auto & cl: p->collections) { + cl.skipDrawingReads = false; + cl.skipDrawingCoverage = true; + } + } else { + p->processed = false; + } + p->redraw = true; + p->imageCache.clear(); + return true; + } + + bool soft_clips(Plot p) { + p->opts.soft_clip_threshold = (p->opts.soft_clip_threshold == 0) ? std::stoi(p->opts.myIni["view_thresholds"]["soft_clip"]) : 0; + if (p->mode == Manager::Show::SINGLE) { + p->processed = true; + } else { + p->processed = false; + } + p->redraw = true; + p->imageCache.clear(); + return true; + } + bool link(Plot p, std::string& command, std::vector& parts) { bool relink = false; if (command == "link" || command == "link all") { @@ -212,6 +268,96 @@ namespace Commands { return false; } + bool indelLength(Plot p, std::vector parts) { + int indel_length = p->opts.indel_length; + try { + indel_length = std::stoi(parts.back()); + } catch (...) { + std::cerr << termcolor::red << "Error:" << termcolor::reset << " indel-length invalid value\n"; + return false; + } + p->opts.indel_length = indel_length; + if (p->mode == Manager::Show::SINGLE) { + p->processed = true; + } else { + p->processed = false; + } + p->redraw = true; + p->imageCache.clear(); + return false; + } + + bool remove(Plot p, std::vector parts) { + int ind = 0; + if (Utils::startsWith(parts.back(), "bam")) { + parts.back().erase(0, 3); + try { + ind = std::stoi(parts.back()); + } catch (...) { + std::cerr << termcolor::red << "Error:" << termcolor::reset << " bam index not understood\n"; + return false; + } + p->removeBam(ind); + } else if (Utils::startsWith(parts.back(), "track")) { + parts.back().erase(0, 5); + try { + ind = std::stoi(parts.back()); + } catch (...) { + std::cerr << termcolor::red << "Error:" << termcolor::reset << " track index not understood\n"; + return false; + } + p->removeTrack(ind); + } else { + try { + ind = std::stoi(parts.back()); + } catch (...) { + std::cerr << termcolor::red << "Error:" << termcolor::reset << " region index not understood\n"; + return false; + } + p->removeRegion(ind); + } + bool clear_filters = false; // removing a region can invalidate indexes so remove them + for (auto &f : p->filters) { + if (!f.targetIndexes.empty()) { + clear_filters = true; + break; + } + } + if (clear_filters) { + p->filters.clear(); + } + return false; + } + + bool cov(Plot p, std::vector parts) { + if (parts.size() > 2) { + std::cerr << termcolor::red << "Error:" << termcolor::reset << " cov must be either 'cov' to toggle coverage or 'cov NUMBER' to set max coverage\n"; + return false; + } else if (parts.size() == 1) { + if (p->opts.max_coverage == 0) { + p->opts.max_coverage = 10000000; + } else { + p->opts.max_coverage = 0; + } + } else { + try { + p->opts.max_coverage = std::stoi(parts.back()); + } catch (...) { + std::cerr << termcolor::red << "Error:" << termcolor::reset << " 'cov NUMBER' not understood\n"; + return false; + } + } + for (auto &cl : p->collections) { + cl.skipDrawingReads = false; + cl.skipDrawingCoverage = false; + } + p->opts.max_coverage = std::max(0, p->opts.max_coverage); + p->redraw = true; + p->processed = false; + p->imageCache.clear(); + return false; + } + // Command functions can access these parameters only #define PARAMS [](Commands::Plot p, std::string& command, std::vector& parts) -> bool @@ -231,6 +377,12 @@ namespace Commands { {"line", PARAMS { return line(p); }}, {"settings", PARAMS { return settings(p); }}, {"sam", PARAMS { return sam(p); }}, + {"ins", PARAMS { return insertions(p); }}, + {"insertions", PARAMS { return insertions(p); }}, + {"mm", PARAMS { return mismatches(p); }}, + {"mismatches", PARAMS { return mismatches(p); }}, + {"edges", PARAMS { return edges(p); }}, + {"soft_clips", PARAMS { return soft_clips(p); }}, {"help", PARAMS { return getHelp(p, command, parts); }}, {"man", PARAMS { return getHelp(p, command, parts); }}, {"link", PARAMS { return link(p, command, parts); }}, @@ -240,6 +392,9 @@ namespace Commands { {"f", PARAMS { return findRead(p, parts); }}, {"find", PARAMS { return findRead(p, parts); }}, {"ylim", PARAMS { return setYlim(p, parts); }}, + {"indel-length", PARAMS { return indelLength(p, parts); }}, + {"remove", PARAMS { return remove(p, parts); }}, + {"cov", PARAMS { return cov(p, parts); }}, }; diff --git a/src/plot_controls.cpp b/src/plot_controls.cpp index cba3564..63acaff 100644 --- a/src/plot_controls.cpp +++ b/src/plot_controls.cpp @@ -522,158 +522,7 @@ namespace Manager { return false; } - if (Utils::startsWith(inputText, "indel-length")) { - std::vector split = Utils::split(inputText, delim); - try { - opts.indel_length = std::stoi(split.back()); - } catch (...) { - std::cerr << termcolor::red << "Error:" << termcolor::reset << " indel-length invalid value\n"; - } - if (mode == SINGLE) { - processed = true; - } else { - processed = false; - } - redraw = true; - inputText = ""; - imageCache.clear(); - return true; - } - else if (inputText =="insertions" || inputText == "ins") { - opts.small_indel_threshold = (opts.small_indel_threshold == 0) ? std::stoi(opts.myIni["view_thresholds"]["small_indel"]) : 0; - if (mode == SINGLE) { - processed = true; - } else { - processed = false; - } - redraw = true; - inputText = ""; - imageCache.clear(); - return true; - } else if (inputText =="mismatches" || inputText == "mm") { - opts.snp_threshold = (opts.snp_threshold == 0) ? std::stoi(opts.myIni["view_thresholds"]["snp"]) : 0; - if (mode == SINGLE) { - processed = true; - for (auto &cl : collections) { - cl.skipDrawingReads = false; - cl.skipDrawingCoverage = false; - } - } else { - processed = false; - } - redraw = true; - inputText = ""; - imageCache.clear(); - return true; - } else if (inputText =="edges") { - opts.edge_highlights = (opts.edge_highlights == 0) ? std::stoi(opts.myIni["view_thresholds"]["edge_highlights"]) : 0; - if (mode == SINGLE) { - processed = true; - for (auto & cl: collections) { - cl.skipDrawingReads = false; - cl.skipDrawingCoverage = true; - } - } else { - processed = false; - } - redraw = true; - inputText = ""; - imageCache.clear(); - return true; - } else if (inputText =="soft-clips" || inputText == "sc") { - opts.soft_clip_threshold = (opts.soft_clip_threshold == 0) ? std::stoi(opts.myIni["view_thresholds"]["soft_clip"]) : 0; - if (mode == SINGLE) { - processed = true; - } else { - processed = false; - } - redraw = true; - inputText = ""; - imageCache.clear(); - return true; - } else if (Utils::startsWith(inputText, "remove ") || Utils::startsWith(inputText, "rm ")) { - std::vector split = Utils::split(inputText, delim); - int ind = 0; - if (Utils::startsWith(split.back(), "bam")) { - split.back().erase(0, 3); - try { - ind = std::stoi(split.back()); - } catch (...) { - std::cerr << termcolor::red << "Error:" << termcolor::reset << " bam index not understood\n"; - inputText = ""; - return true; - } - removeBam(ind); - return true; - } else if (Utils::startsWith(split.back(), "track")) { - split.back().erase(0, 5); - try { - ind = std::stoi(split.back()); - } catch (...) { - std::cerr << termcolor::red << "Error:" << termcolor::reset << " track index not understood\n"; - inputText = ""; - return true; - } - removeTrack(ind); - return true; - } else { - try { - ind = std::stoi(split.back()); - } catch (...) { - std::cerr << termcolor::red << "Error:" << termcolor::reset << " region index not understood\n"; - inputText = ""; - return true; - } - removeRegion(ind); - return true; - } - - bool clear_filters = false; // removing a region can invalidate indexes so remove them - for (auto &f : filters) { - if (!f.targetIndexes.empty()) { - clear_filters = true; - break; - } - } - if (clear_filters) { - filters.clear(); - } - } else if (Utils::startsWith(inputText, "cov")) { - std::vector split = Utils::split(inputText, delim); - if (split.size() > 2) { - std::cerr << termcolor::red << "Error:" << termcolor::reset << " cov must be either 'cov' to toggle coverage or 'cov NUMBER' to set max coverage\n"; - inputText = ""; - return true; - } - else if (split.size() == 1) { - if (opts.max_coverage == 0) { - opts.max_coverage = 10000000; - } else { - opts.max_coverage = 0; - } - - } else { - try { - opts.max_coverage = std::stoi(split.back()); - } catch (...) { - std::cerr << termcolor::red << "Error:" << termcolor::reset << " 'cov NUMBER' not understood\n"; - return true; - } - } - opts.max_coverage = std::max(0, opts.max_coverage); - - for (auto &cl : collections) { - cl.skipDrawingReads = false; - cl.skipDrawingCoverage = false; - } - redraw = true; - processed = false; - - inputText = ""; - imageCache.clear(); - return true; - } - else if (inputText == "log2-cov") { + if (inputText == "log2-cov") { opts.log2_cov = !(opts.log2_cov); redraw = true; if (mode == SINGLE) { From b20043e5380f546c916cc4f794cc267b8c65a2d9 Mon Sep 17 00:00:00 2001 From: kcleal Date: Mon, 13 May 2024 16:58:31 +0100 Subject: [PATCH 29/51] Command interface done. Option to redirect output to ostringstream. Fixed custom .png bug. Started work on save command --- src/parser.cpp | 44 +-- src/parser.h | 2 +- src/plot_commands.cpp | 801 +++++++++++++++++++++++++++++++++++++----- src/plot_commands.h | 2 +- src/plot_controls.cpp | 602 ++++--------------------------- src/plot_manager.cpp | 59 ++-- src/plot_manager.h | 9 +- src/term_out.cpp | 585 ++++++++++++++---------------- src/term_out.h | 27 +- 9 files changed, 1115 insertions(+), 1016 deletions(-) diff --git a/src/parser.cpp b/src/parser.cpp index 0ce02dc..208d2cd 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -659,7 +659,7 @@ namespace Parse { void countExpression(std::vector &collections, std::string &str, std::vector hdrs, - std::vector &bam_paths, int nBams, int nRegions) { + std::vector &bam_paths, int nBams, int nRegions, std::ostream& out) { std::vector filters; for (auto &s: Utils::split(str, ';')) { @@ -726,34 +726,34 @@ namespace Parse { inv_r += 1; } } - std::cout << termcolor::bright_blue << "File\t" << bam_paths[col.bamIdx] << termcolor::reset << std::endl; - std::cout << "Region\t" << col.region->chrom << ":" << col.region->start << "-" << col.region->end << std::endl; + out << termcolor::bright_blue << "File\t" << bam_paths[col.bamIdx] << termcolor::reset << std::endl; + out << "Region\t" << col.region->chrom << ":" << col.region->start << "-" << col.region->end << std::endl; if (!str.empty()) { - std::cout << "Filter\t" << str << std::endl; + out << "Filter\t" << str << std::endl; } - std::cout << "Total\t" << tot << std::endl; - std::cout << "Paired\t" << paired << std::endl; - std::cout << "Proper-pair\t" << proper_pair << std::endl; - std::cout << "Read-unmapped\t" << read_unmapped << std::endl; - std::cout << "Mate-unmapped\t" << mate_unmapped << std::endl; - std::cout << "Read-reverse\t" << read_reverse << std::endl; - std::cout << "Mate-reverse\t" << mate_reverse << std::endl; - std::cout << "First-in-pair\t" << first << std::endl; - std::cout << "Second-in-pair\t" << second << std::endl; - std::cout << "Not-primary\t" << not_primary << std::endl; - std::cout << "Fails-qc\t" << fails_qc << std::endl; - std::cout << "Duplicate\t" << duplicate << std::endl; - std::cout << "Supplementary\t" << supp << std::endl; + out << "Total\t" << tot << std::endl; + out << "Paired\t" << paired << std::endl; + out << "Proper-pair\t" << proper_pair << std::endl; + out << "Read-unmapped\t" << read_unmapped << std::endl; + out << "Mate-unmapped\t" << mate_unmapped << std::endl; + out << "Read-reverse\t" << read_reverse << std::endl; + out << "Mate-reverse\t" << mate_reverse << std::endl; + out << "First-in-pair\t" << first << std::endl; + out << "Second-in-pair\t" << second << std::endl; + out << "Not-primary\t" << not_primary << std::endl; + out << "Fails-qc\t" << fails_qc << std::endl; + out << "Duplicate\t" << duplicate << std::endl; + out << "Supplementary\t" << supp << std::endl; if (del > 0) - std::cout << "Deletion-pattern\t" << del << std::endl; + out << "Deletion-pattern\t" << del << std::endl; if (dup > 0) - std::cout << "Duplication-pattern\t" << dup << std::endl; + out << "Duplication-pattern\t" << dup << std::endl; if (tra > 0) - std::cout << "Translocation-pattern\t" << tra << std::endl; + out << "Translocation-pattern\t" << tra << std::endl; if (inv_f > 0) - std::cout << "F-inversion-pattern\t" << inv_f << std::endl; + out << "F-inversion-pattern\t" << inv_f << std::endl; if (inv_r > 0) - std::cout << "R-inversion-pattern\t" << inv_r << std::endl; + out << "R-inversion-pattern\t" << inv_r << std::endl; } } diff --git a/src/parser.h b/src/parser.h index 561368e..75ab0d6 100644 --- a/src/parser.h +++ b/src/parser.h @@ -103,7 +103,7 @@ namespace Parse { }; void countExpression(std::vector &collections, std::string &str, std::vector hdrs, - std::vector &bam_paths, int nBams, int nRegions); + std::vector &bam_paths, int nBams, int nRegions, std::ostream& out); void parse_INFO(std::string &line, std::string &infoCol, std::string &request); diff --git a/src/plot_commands.cpp b/src/plot_commands.cpp index e58c04a..e39e277 100644 --- a/src/plot_commands.cpp +++ b/src/plot_commands.cpp @@ -13,8 +13,21 @@ #include #include #include +#include +#include +#include #include -#include "hts_funcs.h" +#include +#include +#include +#include + +#if defined(_WIN32) + +#else + #include +#endif + #include "parser.h" #include "plot_manager.h" #include "term_out.h" @@ -23,67 +36,193 @@ namespace Commands { - using Plot = Manager::GwPlot*; + enum Err { + NONE, + UNKNOWN, + SILENT, + + TOO_MANY_OPTIONS, + CHROM_NOT_IN_REFERENCE, + FEATURE_NOT_IN_TRACKS, + BAD_REGION, + OPTION_NOT_UNDERSTOOD, + INVALID_PATH, + EMPTY_TRACKS, + EMPTY_BAMS, + EMPTY_REGIONS, + EMPTY_VARIANTS, + + PARSE_VCF, + PARSE_INPUT, - bool noOp(Plot p) { + }; + + using Plot = Manager::GwPlot; + + Err noOp(Plot* p) { p->redraw = false; p->processed = true; - return false; + return Err::NONE; } - bool triggerClose(Plot p) { + Err triggerClose(Plot* p) { p->triggerClose = true; p->redraw = false; p->processed = true; - return false; + return Err::NONE; } - bool getHelp(Plot p, std::string& command, std::vector& parts) { - if (command == "help" || command == "man") { - Term::help(p->opts); + Err getHelp(Plot* p, std::string& command, std::vector& parts, std::ostream& out) { + if (command == "h" || command == "help" || command == "man") { + Term::help(p->opts, out); } else if (parts.size() == 2) { - Term::manuals(parts[1]); + Term::manuals(parts[1], out); } p->redraw = false; p->processed = true; - return false; + return Err::NONE; } - bool refreshGw(Plot p) { + Err refreshGw(Plot* p) { p->redraw = true; p->processed = false; p->imageCache.clear(); p->filters.clear(); p->target_qname = ""; for (auto &cl: p->collections) { cl.vScroll = 0; } - return false; + return Err::NONE; } - bool line(Plot p) { + Err line(Plot* p) { p->drawLine = !p->drawLine; p->redraw = true; p->processed = true; - return false; + return Err::NONE; } - bool settings(Plot p) { + Err settings(Plot* p) { p->last_mode = p->mode; p->mode = Manager::Show::SETTINGS; p->redraw = true; p->processed = true; - return false; + return Err::NONE; + } + + std::string tilde_to_home(std::string fpath) { + if (fpath[0] != '~') { + return fpath; + } + fpath.erase(0, 2); +#if defined(_WIN32) || defined(_WIN64) + const char *homedrive_c = std::getenv("HOMEDRIVE"); + const char *homepath_c = std::getenv("HOMEPATH"); + std::string homedrive(homedrive_c ? homedrive_c : ""); + std::string homepath(homepath_c ? homepath_c : ""); + std::string home = homedrive + homepath; +#else + struct passwd *pw = getpwuid(getuid()); + std::string home(pw->pw_dir); +#endif + std::filesystem::path path; + std::filesystem::path homedir(home); + std::filesystem::path inputPath(fpath); + path = homedir / inputPath; + return path; } - bool sam(Plot p) { + Err sam(Plot* p, std::string& command, std::vector& parts, std::ostream& out) { if (!p->selectedAlign.empty()) { - Term::printSelectedSam(p->selectedAlign); + if (command == "sam") { + Term::printSelectedSam(p->selectedAlign, out); + } else if (parts.size() == 3 && (Utils::endsWith(parts[2], ".sam") || Utils::endsWith(parts[2], ".bam") || Utils::endsWith(parts[2], ".cram"))) { + std::string o_str = parts[2]; + sam_hdr_t* hdr = p->headers[p->regionSelection]; + cram_fd* fc = nullptr; + htsFile *h_out = nullptr; + bool write_cram = false; + int res; + std::string full_path = tilde_to_home(parts[2]); + const char* outf = full_path.c_str(); + if (parts[1] == ">") { + out << "Creating new file: " << outf << "\n"; + if (Utils::endsWith(o_str, ".sam")) { + h_out = hts_open(outf, "w"); + res = sam_hdr_write(h_out, hdr); + if (res < 0) { + out << termcolor::red << "Error:" << termcolor::reset << " Failed to copy header\n"; + sam_close(h_out); + return Err::NONE; + } + } else if (Utils::endsWith(o_str, ".bam")) { + h_out = hts_open(outf, "wb"); + res = sam_hdr_write(h_out, hdr); + if (res < 0) { + out << termcolor::red << "Error:" << termcolor::reset << " Failed to copy header\n"; + sam_close(h_out); + return Err::NONE; + } + } else { + h_out = hts_open(outf, "wc"); + fc = h_out->fp.cram; + write_cram = true; + cram_fd_set_header(fc, hdr); + res = sam_hdr_write(h_out, hdr); + if (res < 0) { + out << termcolor::red << "Error:" << termcolor::reset << " Failed to copy header\n"; + sam_close(h_out); + return Err::NONE; + } + } + } else if (parts[1] == ">>") { + out << "Appending to file: " << outf << "\n"; + if (Utils::endsWith(o_str, ".sam")) { + h_out = hts_open(outf, "a"); + } else if (Utils::endsWith(o_str, ".bam")) { + h_out = hts_open(outf, "ab"); + } else { + h_out = hts_open(outf, "ac"); + fc = h_out->fp.cram; + write_cram = true; + } + } + bam1_t* b = bam_init1(); + kstring_t kstr = {0, 0, nullptr}; + std::string& stdStr = p->selectedAlign; + kstr.l = stdStr.size(); + kstr.m = stdStr.size() + 1; + kstr.s = (char*)malloc(kstr.m); + memcpy(kstr.s, stdStr.data(), stdStr.size()); + kstr.s[kstr.l] = '\0'; + std::cerr << kstr.s << std::endl; + res = sam_parse1(&kstr, hdr, b); + free(kstr.s); + if (res < 0) { + out << termcolor::red << "Error:" << termcolor::reset << " Could not convert str to bam1_t\n"; + bam_destroy1(b); + sam_close(h_out); + return Err::NONE; + } + if (!write_cram) { + hts_set_fai_filename(h_out, p->reference.c_str()); + hts_set_threads(h_out, p->opts.threads); + } else { + cram_set_option(fc, CRAM_OPT_REFERENCE, p->fai); + cram_set_option(fc, CRAM_OPT_NTHREADS, p->opts.threads); + } + res = sam_write1(h_out, hdr, b); + if (res < 0) { + out << termcolor::red << "Error:" << termcolor::reset << "Write failed\n"; + } + bam_destroy1(b); + sam_close(h_out); + } } p->redraw = false; p->processed = true; - return false; + return Err::NONE; } - bool insertions(Plot p) { + Err insertions(Plot* p) { p->opts.small_indel_threshold = (p->opts.small_indel_threshold == 0) ? std::stoi(p->opts.myIni["view_thresholds"]["small_indel"]) : 0; if (p->mode == Manager::Show::SINGLE) { p->processed = true; @@ -92,10 +231,10 @@ namespace Commands { } p->redraw = true; p->imageCache.clear(); - return false; + return Err::NONE; } - bool mismatches(Plot p) { + Err mismatches(Plot* p) { p->opts.snp_threshold = (p->opts.snp_threshold == 0) ? std::stoi(p->opts.myIni["view_thresholds"]["snp"]) : 0; if (p->mode == Manager::Show::SINGLE) { p->processed = true; @@ -108,10 +247,10 @@ namespace Commands { } p->redraw = true; p->imageCache.clear(); - return true; + return Err::NONE; } - bool edges(Plot p) { + Err edges(Plot* p) { p->opts.edge_highlights = (p->opts.edge_highlights == 0) ? std::stoi(p->opts.myIni["view_thresholds"]["edge_highlights"]) : 0; if (p->mode == Manager::Show::SINGLE) { p->processed = true; @@ -124,10 +263,10 @@ namespace Commands { } p->redraw = true; p->imageCache.clear(); - return true; + return Err::NONE; } - bool soft_clips(Plot p) { + Err soft_clips(Plot* p) { p->opts.soft_clip_threshold = (p->opts.soft_clip_threshold == 0) ? std::stoi(p->opts.myIni["view_thresholds"]["soft_clip"]) : 0; if (p->mode == Manager::Show::SINGLE) { p->processed = true; @@ -136,10 +275,53 @@ namespace Commands { } p->redraw = true; p->imageCache.clear(); - return true; + return Err::NONE; + } + + Err log2_cov(Plot* p) { + p->opts.log2_cov = !(p->opts.log2_cov); + p->redraw = true; + if (p->mode == Manager::Show::SINGLE) { + p->processed = true; + for (auto &cl : p->collections) { + cl.skipDrawingReads = false; + cl.skipDrawingCoverage = false; + } + } else { + p->processed = false; + } + p->imageCache.clear(); + return Err::NONE; + } + + Err expand_tracks(Plot* p) { + p->opts.expand_tracks = !(p->opts.expand_tracks); + p->redraw = true; + if (p->mode == Manager::Show::SINGLE) { + p->processed = true; + } else { + p->processed = false; + } + p->imageCache.clear(); + return Err::NONE; + } + + Err tlen_y(Plot* p) { + if (!p->opts.tlen_yscale) { + p->opts.max_tlen = 2000; + p->opts.ylim = 2000; + p->samMaxY = 2000; + } else { + p->opts.ylim = 60; + p->samMaxY = 60; + } + p->opts.tlen_yscale = !p->opts.tlen_yscale; + p->processed = false; + p->redraw = true; + return Err::NONE; } - bool link(Plot p, std::string& command, std::vector& parts) { + Err link(Plot* p, std::string& command, std::vector& parts) { bool relink = false; if (command == "link" || command == "link all") { relink = (p->opts.link_op != 2) ? true : false; @@ -159,90 +341,194 @@ namespace Commands { p->redraw = true; p->processed = true; } - return false; + return Err::NONE; + } + + Err var_info(Plot* p, std::string& command, std::vector& parts, std::ostream& out) { + if (p->variantTracks.empty()) { + return Err::EMPTY_VARIANTS; + } + p->currentVarTrack = &p->variantTracks[p->variantFileSelection]; + if (p->currentVarTrack->multiLabels.empty()) { + return Err::EMPTY_VARIANTS; + } else if (p->currentVarTrack->blockStart + p->mouseOverTileIndex >= (int)p->currentVarTrack->multiLabels.size() || p->mouseOverTileIndex == -1) { + return Err::SILENT; + } + Utils::Label &lbl = p->currentVarTrack->multiLabels[p->currentVarTrack->blockStart + p->mouseOverTileIndex]; + Term::clearLine(out); + if (p->currentVarTrack->type == HGW::TrackType::VCF) { + p->currentVarTrack->vcf.printTargetRecord(lbl.variantId, lbl.chrom, lbl.pos); + std::string variantStringCopy = p->currentVarTrack->vcf.variantString; + p->currentVarTrack->vcf.get_samples(); + std::vector sample_names_copy = p->currentVarTrack->vcf.sample_names; + if (variantStringCopy.empty()) { + out << termcolor::red << "Error:" << termcolor::reset << " could not parse vcf/bcf line"; + return Err::PARSE_VCF; + } else { + size_t requests = parts.size(); + if (requests == 1) { + Term::clearLine(out); + out << "\r" << variantStringCopy << std::endl; + } else { + std::string requestedVars; + std::vector vcfCols = Utils::split(variantStringCopy, '\t'); + for (size_t i = 1; i < requests; ++i) { + std::string result; + try { + Parse::parse_vcf_split(result, vcfCols, parts[i], sample_names_copy); + } catch (...) { + out << termcolor::red << "Error:" << termcolor::reset << " could not parse " << parts[i] << std::endl; + return Err::PARSE_VCF; + } + if (i != requests-1) { + requestedVars += parts[i] + ": " + result + "\t"; + } else { + requestedVars += parts[i] + ": " + result; + } + } + if (!requestedVars.empty()) { + Term::clearLine(out); + out << "\r" << requestedVars << std::endl; + } + } + } + } else { + p->currentVarTrack->variantTrack.printTargetRecord(lbl.variantId, lbl.chrom, lbl.pos); + if (p->currentVarTrack->variantTrack.variantString.empty()) { + Term::clearLine(out); + out << "\r" << p->currentVarTrack->variantTrack.variantString << std::endl; + } else { + out << termcolor::red << "Error:" << termcolor::reset << " could not parse variant line"; + return Err::PARSE_VCF; + } + } + return Err::NONE; } - bool count(Plot p, std::string& command) { + Err count(Plot* p, std::string& command, std::ostream& out) { std::string str = command; str.erase(0, 6); - Parse::countExpression(p->collections, str, p->headers, p->bam_paths, (int)p->bams.size(), (int)p->regions.size()); + Parse::countExpression(p->collections, str, p->headers, p->bam_paths, (int)p->bams.size(), (int)p->regions.size(), out); p->redraw = false; p->processed = true; - return false; + return Err::NONE; } - bool addFilter(Plot p, std::string& command) { + Err addFilter(Plot* p, std::string& command, std::ostream& out) { std::string str = command; str.erase(0, 7); if (str.empty()) { - return false; + return Err::NONE; } for (auto &s: Utils::split(str, ';')) { Parse::Parser ps = Parse::Parser(); int rr = ps.set_filter(s, (int)p->bams.size(), (int)p->regions.size()); if (rr > 0) { p->filters.push_back(ps); - std::cout << command << std::endl; + out << command << std::endl; } } p->imageCache.clear(); p->redraw = true; p->processed = false; - return false; + return Err::NONE; } - bool tags(Plot p, std::string& command) { + Err tags(Plot* p, std::string& command, std::ostream& out) { if (!p->selectedAlign.empty()) { std::string str = command; str.erase(0, 4); std::vector splitTags = Utils::split(str, ' '); std::vector splitA = Utils::split(p->selectedAlign, '\t'); if (splitA.size() > 11) { - Term::clearLine(); - std::cout << "\r"; + Term::clearLine(out); + out << "\r"; int i = 0; for (auto &s : splitA) { if (i > 11) { std::string t = s.substr(0, s.find(':')); if (splitTags.empty()) { std::string rest = s.substr(s.find(':'), s.size()); - std::cout << termcolor::green << t << termcolor::reset << rest << "\t"; + out << termcolor::green << t << termcolor::reset << rest << "\t"; } else { for (auto &target : splitTags) { if (target == t) { std::string rest = s.substr(s.find(':'), s.size()); - std::cout << termcolor::green << t << termcolor::reset << rest << "\t"; + out << termcolor::green << t << termcolor::reset << rest << "\t"; } } } } i += 1; } - std::cout << std::endl; + out << std::endl; } } p->redraw = false; p->processed = true; - return false; + return Err::NONE; } - bool findRead(Plot p, std::vector parts) { + Err mate(Plot* p, std::string& command, std::ostream& out) { + std::string mate; + Utils::parseMateLocation(p->selectedAlign, mate, p->target_qname); + if (mate.empty()) { + out << termcolor::red << "Error:" << termcolor::reset << " could not parse mate location\n"; + return Err::NONE; + } + if (p->regionSelection >= 0 && p->regionSelection < (int)p->regions.size()) { + if (command == "mate") { + p->regions[p->regionSelection] = Utils::parseRegion(mate); + p->processed = false; + for (auto &cl: p->collections) { + if (cl.regionIdx == p->regionSelection) { + cl.region = &(p->regions)[p->regionSelection]; + cl.readQueue.clear(); + cl.covArr.clear(); + cl.levelsStart.clear(); + cl.levelsEnd.clear(); + } + } + p->processBam(); + p->highlightQname(); + p->redraw = true; + if (p->mode == Manager::Show::SINGLE) { + p->processed = true; + } else { + p->processed = false; + } + p->imageCache.clear(); + } else if (command == "mate add" && p->mode == Manager::Show::SINGLE) { + p->regions.push_back(Utils::parseRegion(mate)); + p->fetchRefSeq(p->regions.back()); + p->processed = false; + p->processBam(); + p->highlightQname(); + p->redraw = true; + p->processed = true; + p->imageCache.clear(); + } + } + return Err::NONE; + } + + Err findRead(Plot* p, std::vector parts, std::ostream& out) { if (!p->target_qname.empty() && parts.size() == 1) { - return false; + return Err::NONE; } else if (parts.size() == 2) { p->target_qname = parts.back(); } else { - std::cerr << termcolor::red << "Error:" << termcolor::reset << " please provide one qname\n"; - return false; + out << termcolor::red << "Error:" << termcolor::reset << " please provide one qname\n"; + return Err::NONE; } p->highlightQname(); p->redraw = true; p->processed = true; p->imageCache.clear(); - return false; + return Err::NONE; } - bool setYlim(Plot p, std::vector parts) { + Err setYlim(Plot* p, std::vector parts, std::ostream& out) { int ylim = p->opts.ylim; int samMaxY = p->opts.ylim; int max_tlen = p->opts.max_tlen; @@ -255,8 +541,8 @@ namespace Commands { samMaxY = p->opts.max_tlen; } } catch (...) { - std::cerr << termcolor::red << "Error:" << termcolor::reset << " ylim invalid value\n"; - return false; + out << termcolor::red << "Error:" << termcolor::reset << " ylim invalid value\n"; + return Err::NONE; } p->imageCache.clear(); HGW::refreshLinked(p->collections, p->opts, &p->samMaxY); @@ -265,16 +551,16 @@ namespace Commands { p->opts.ylim = ylim; p->samMaxY = samMaxY; p->opts.max_tlen = max_tlen; - return false; + return Err::NONE; } - bool indelLength(Plot p, std::vector parts) { + Err indelLength(Plot* p, std::vector parts, std::ostream& out) { int indel_length = p->opts.indel_length; try { indel_length = std::stoi(parts.back()); } catch (...) { - std::cerr << termcolor::red << "Error:" << termcolor::reset << " indel-length invalid value\n"; - return false; + out << termcolor::red << "Error:" << termcolor::reset << " indel-length invalid value\n"; + return Err::NONE; } p->opts.indel_length = indel_length; if (p->mode == Manager::Show::SINGLE) { @@ -284,18 +570,18 @@ namespace Commands { } p->redraw = true; p->imageCache.clear(); - return false; + return Err::NONE; } - bool remove(Plot p, std::vector parts) { + Err remove(Plot* p, std::vector parts, std::ostream& out) { int ind = 0; if (Utils::startsWith(parts.back(), "bam")) { parts.back().erase(0, 3); try { ind = std::stoi(parts.back()); } catch (...) { - std::cerr << termcolor::red << "Error:" << termcolor::reset << " bam index not understood\n"; - return false; + out << termcolor::red << "Error:" << termcolor::reset << " bam index not understood\n"; + return Err::NONE; } p->removeBam(ind); } else if (Utils::startsWith(parts.back(), "track")) { @@ -303,16 +589,16 @@ namespace Commands { try { ind = std::stoi(parts.back()); } catch (...) { - std::cerr << termcolor::red << "Error:" << termcolor::reset << " track index not understood\n"; - return false; + out << termcolor::red << "Error:" << termcolor::reset << " track index not understood\n"; + return Err::NONE; } p->removeTrack(ind); } else { try { ind = std::stoi(parts.back()); } catch (...) { - std::cerr << termcolor::red << "Error:" << termcolor::reset << " region index not understood\n"; - return false; + out << termcolor::red << "Error:" << termcolor::reset << " region index not understood\n"; + return Err::NONE; } p->removeRegion(ind); } @@ -326,13 +612,13 @@ namespace Commands { if (clear_filters) { p->filters.clear(); } - return false; + return Err::NONE; } - bool cov(Plot p, std::vector parts) { + Err cov(Plot* p, std::vector parts, std::ostream& out) { if (parts.size() > 2) { - std::cerr << termcolor::red << "Error:" << termcolor::reset << " cov must be either 'cov' to toggle coverage or 'cov NUMBER' to set max coverage\n"; - return false; + out << termcolor::red << "Error:" << termcolor::reset << " cov must be either 'cov' to toggle coverage or 'cov NUMBER' to set max coverage\n"; + return Err::NONE; } else if (parts.size() == 1) { if (p->opts.max_coverage == 0) { p->opts.max_coverage = 10000000; @@ -343,8 +629,8 @@ namespace Commands { try { p->opts.max_coverage = std::stoi(parts.back()); } catch (...) { - std::cerr << termcolor::red << "Error:" << termcolor::reset << " 'cov NUMBER' not understood\n"; - return false; + out << termcolor::red << "Error:" << termcolor::reset << " 'cov NUMBER' not understood\n"; + return Err::NONE; } } for (auto &cl : p->collections) { @@ -355,18 +641,331 @@ namespace Commands { p->redraw = true; p->processed = false; p->imageCache.clear(); - return false; + return Err::NONE; } - // Command functions can access these parameters only - #define PARAMS [](Commands::Plot p, std::string& command, std::vector& parts) -> bool + Err theme(Plot* p, std::vector parts, std::ostream& out) { + if (parts.size() != 2) { + out << termcolor::red << "Error:" << termcolor::reset << " theme must be either 'igv', 'dark' or 'slate'\n"; + return Err::NONE; + } + if (parts.back() == "dark") { + p->opts.theme = Themes::DarkTheme(); p->opts.theme.setAlphas(); p->imageCache.clear(); p->opts.theme_str = "dark"; + } else if (parts.back() == "igv") { + p->opts.theme = Themes::IgvTheme(); p->opts.theme.setAlphas(); p->imageCache.clear(); p->opts.theme_str = "igv"; + } else if (parts.back() == "slate") { + p->opts.theme = Themes::SlateTheme(); p->opts.theme.setAlphas(); p->imageCache.clear(); p->opts.theme_str = "slate"; + } else { + return Err::OPTION_NOT_UNDERSTOOD; + } + p->processed = false; + p->redraw = true; + return Err::NONE; + } + + Err goto_command(Plot* p, std::vector parts) { + Err reason = Err::NONE; + if (parts.size() > 1 && parts.size() < 4) { + int index = p->regionSelection; + Utils::Region rgn; + try { + rgn = Utils::parseRegion(parts[1]); + int res = faidx_has_seq(p->fai, rgn.chrom.c_str()); + if (res <= 0) { + reason = Err::CHROM_NOT_IN_REFERENCE; + } + } catch (...) { + reason = Err::BAD_REGION; + } + if (reason == Err::NONE) { + if (p->mode != Manager::Show::SINGLE) { p->mode = Manager::Show::SINGLE; } + if (p->regions.empty()) { + p->regions.push_back(rgn); + p->fetchRefSeq(p->regions.back()); + } else { + if (index < (int)p->regions.size()) { + if (p->regions[index].chrom == rgn.chrom) { + rgn.markerPos = p->regions[index].markerPos; + rgn.markerPosEnd = p->regions[index].markerPosEnd; + } + p->regions[index] = rgn; + p->fetchRefSeq(p->regions[index]); + } + } + } else { // search all tracks for matching name, slow but ok for small tracks + if (!p->tracks.empty()) { + bool res = HGW::searchTracks(p->tracks, parts[1], rgn); + if (res) { + if (p->mode != Manager::Show::SINGLE) { p->mode = Manager::Show::SINGLE; } + if (p->regions.empty()) { + p->regions.push_back(rgn); + p->fetchRefSeq(p->regions.back()); + } else { + if (index < (int)p->regions.size()) { + p->regions[index] = rgn; + p->fetchRefSeq(p->regions[index]); + } + } + } else { + reason = Err::SILENT; + } + } + } + } + if (reason == Err::NONE) { + p->redraw = true; + p->processed = false; + } + return Err::NONE; + } + + Err grid(Plot* p, std::vector parts) { + try { + p->opts.number = Utils::parseDimensions(parts[1]); + } catch (...) { + return Err::PARSE_INPUT; + } + p->redraw = true; + p->processed = false; + p->imageCache.clear(); + return Err::NONE; + } + + Err add_region(Plot* p, std::vector parts, std::ostream& out) { + if (p->mode != Manager::Show::SINGLE) { + return Err::NONE; + } + if (parts.size() <= 1) { + out << termcolor::red << "Error:" << termcolor::reset << " expected a Region e.g. chr1:1-20000\n"; + return Err::PARSE_INPUT; + } + std::vector new_regions; + for (size_t i=1; i < parts.size(); ++i) { + if (parts[i] == "mate") { + out << "Did you mean to use the 'mate add' function instead?\n"; + break; + } + try { + Utils::Region dummy_region = Utils::parseRegion(parts[i]); + int res = faidx_has_seq(p->fai, dummy_region.chrom.c_str()); + if (res <= 0) { + return Err::CHROM_NOT_IN_REFERENCE; + } else { + new_regions.push_back(dummy_region); + p->fetchRefSeq(new_regions.back()); + } + } catch (...) { + out << termcolor::red << "Error parsing :add" << termcolor::reset; + return Err::PARSE_INPUT; + } + } + p->regions.insert(p->regions.end(), new_regions.begin(), new_regions.end()); + p->redraw = true; + p->processed = false; + p->imageCache.clear(); + return Err::NONE; + } + + Err snapshot(Plot* p, std::vector parts, std::ostream& out) { + if (parts.size() > 2) { + return Err::TOO_MANY_OPTIONS; + } + std::string fname; + p->currentVarTrack = &p->variantTracks[p->variantFileSelection]; + if (parts.size() == 1) { + if (p->mode == Manager::Show::SINGLE) { + std::filesystem::path fname_path = Utils::makeFilenameFromRegions(p->regions); +#if defined(_WIN32) || defined(_WIN64) + const wchar_t* pc = fname_path.filename().c_str(); + std::wstring ws(pc); + std::string p(ws.begin(), ws.end()); + fname = p; +#else + fname = fname_path.filename(); +#endif + } else if (p->currentVarTrack != nullptr) { + fname = "index_" + std::to_string(p->currentVarTrack->blockStart) + "_" + + std::to_string(p->opts.number.x * p->opts.number.y) + ".png"; + } + } else { + std::string nameFormat = parts[1]; + if (p->currentVarTrack != nullptr && p->currentVarTrack->type == HGW::TrackType::VCF && p->mode == Manager::Show::SINGLE) { + if (p->mouseOverTileIndex == -1 || p->currentVarTrack->blockStart + p->mouseOverTileIndex > (int) p->currentVarTrack->multiLabels.size()) { + return Err::SILENT; + } + Utils::Label &lbl = p->currentVarTrack->multiLabels[p->currentVarTrack->blockStart + p->mouseOverTileIndex]; + p->currentVarTrack->vcf.get_samples(); + std::vector sample_names_copy = p->currentVarTrack->vcf.sample_names; + p->currentVarTrack->vcf.printTargetRecord(lbl.variantId, lbl.chrom, lbl.pos); + std::string variantStringCopy = p->currentVarTrack->vcf.variantString; + if (!variantStringCopy.empty() && variantStringCopy.back() == '\n') { + variantStringCopy.erase(variantStringCopy.length()-1); + } + std::vector vcfCols = Utils::split(variantStringCopy, '\t'); + try { + Parse::parse_output_name_format(nameFormat, vcfCols, sample_names_copy, p->bam_paths, lbl.current()); + } catch (...) { + return Err::PARSE_INPUT; + } + } + fname = nameFormat; + Utils::trim(fname); + } + std::filesystem::path outdir = p->opts.outdir; + std::filesystem::path fname_path(fname); + std::filesystem::path out_path = outdir / fname_path; + if (!std::filesystem::exists(out_path.parent_path()) && !out_path.parent_path().empty()) { + out << termcolor::red << "Error:" << termcolor::reset << " path not found " << out_path.parent_path() << std::endl; + return Err::INVALID_PATH; + } else { + if (!p->imageCacheQueue.empty()) { + Manager::imagePngToFile(p->imageCacheQueue.back().second, out_path.string()); + Term::clearLine(out); + out << "\rSaved to " << out_path << std::endl; + } + } + return Err::NONE; + } + + Err online(Plot* p, std::vector parts, std::ostream& out) { + if (p->regions.empty()) { + return Err::EMPTY_REGIONS; + } + std::string genome_tag; + if (p->opts.genome_tag.empty() && parts.size() >= 2) { + genome_tag = parts[1]; + } else { + genome_tag = p->opts.genome_tag; + } + Term::printOnlineLinks(p->tracks, p->regions[p->regionSelection], genome_tag, out); + return Err::NONE; + } + + Err infer_region_or_feature(Plot* p, std::string& command, std::vector parts) { + Utils::Region rgn; + Err reason = Err::NONE; + try { + rgn = Utils::parseRegion(command); + } catch (...) { + reason = Err::BAD_REGION; + } + if (reason == Err::NONE) { + int res = faidx_has_seq(p->fai, rgn.chrom.c_str()); + if (res <= 0) { + reason = Err::CHROM_NOT_IN_REFERENCE; + } + if (p->mode != Manager::Show::SINGLE) { p->mode = Manager::Show::SINGLE; } + if (p->regions.empty()) { + p->regions.push_back(rgn); + p->fetchRefSeq(p->regions.back()); + } else { + if (p->regions[p->regionSelection].chrom == rgn.chrom) { + rgn.markerPos = p->regions[p->regionSelection].markerPos; + rgn.markerPosEnd = p->regions[p->regionSelection].markerPosEnd; + } + p->regions[p->regionSelection] = rgn; + p->fetchRefSeq(p->regions[p->regionSelection]); + } + } else { // search all tracks for matching name, slow but ok for small tracks + if (!p->tracks.empty()) { + bool res = HGW::searchTracks(p->tracks, command, rgn); + if (res) { + if (p->mode != Manager::Show::SINGLE) { p->mode = Manager::Show::SINGLE; } + if (p->regions.empty()) { + p->regions.push_back(rgn); + p->fetchRefSeq(p->regions.back()); + } else { + if (p->regionSelection < (int)p->regions.size()) { + p->regions[p->regionSelection] = rgn; + p->fetchRefSeq(p->regions[p->regionSelection]); + } + } + } else { + reason = Err::SILENT; + } + } + } + if (reason == Err::NONE) { + p->redraw = true; + p->processed = false; + p->imageCache.clear(); + } + return reason; + } + +// Err write_bam() + + Err infer_region_or_feature(Plot* p, std::string& command, std::vector parts) { + if (parts.size() == 1) { + return Err::OPTION_NOT_UNDERSTOOD; + } + if (parts.size() == 2) { + if (Utils::endsWith(parts.back(), ".bam") || Utils::endsWith(parts.back(), ".cram")) { + + } + } else if (parts.size() == 3 && (parts[1] == ">" || parts[1] == ">>") { + + } else { + return Err::OPTION_NOT_UNDERSTOOD; + } + } + + void handle_err(Err result, std::ostream& out) { + switch (result) { + case NONE: break; + case UNKNOWN: + out << termcolor::red << "Error:" << termcolor::reset << " Unknown error\n"; + break; + case SILENT: break; + case TOO_MANY_OPTIONS: + out << termcolor::red << "Error:" << termcolor::reset << " Too many options supplied\n"; + break; + case CHROM_NOT_IN_REFERENCE: + out << termcolor::red << "Error:" << termcolor::reset << " chromosome not in reference\n"; + break; + case FEATURE_NOT_IN_TRACKS: + out << termcolor::red << "Error:" << termcolor::reset << " Feature not in tracks\n"; + break; + case BAD_REGION: + out << termcolor::red << "Error:" << termcolor::reset << " Region not understood\n"; + break; + case OPTION_NOT_UNDERSTOOD: + out << termcolor::red << "Error:" << termcolor::reset << " Option not understood\n"; + break; + case INVALID_PATH: + out << termcolor::red << "Error:" << termcolor::reset << " Path was invalid\n"; + break; + case EMPTY_TRACKS: + out << termcolor::red << "Error:" << termcolor::reset << " tracks are empty (add a track first)\n"; + break; + case EMPTY_BAMS: + out << termcolor::red << "Error:" << termcolor::reset << " Bams are empty (add a bam first)\n"; + break; + case EMPTY_REGIONS: + out << termcolor::red << "Error:" << termcolor::reset << " Regions are empty (add a region first)\n"; + break; + case EMPTY_VARIANTS: + out << termcolor::red << "Error:" << termcolor::reset << " No variant file (add a variant file first)\n"; + break; + case PARSE_VCF: + out << termcolor::red << "Error:" << termcolor::reset << " Vcf parsing error\n"; + break; + case PARSE_INPUT: + out << termcolor::red << "Error:" << termcolor::reset << " Input could not be parsed\n"; + break; + } + } + + // Command functions capture these parameters only + #define PARAMS [](Commands::Plot* p, std::string& command, std::vector& parts, std::ostream& out) -> Err // Note the function map will be cached after first call. plt is bound, but parts are updated with each call - bool run_command_map(Plot p, std::string& command) { + void run_command_map(Plot* p, std::string& command, std::ostream& out) { std::vector parts = Utils::split(command, ' '); - static std::unordered_map&)>> functionMap = { + static std::unordered_map&, std::ostream& out)>> functionMap = { {":", PARAMS { return noOp(p); }}, {"/", PARAMS { return noOp(p); }}, @@ -376,40 +975,52 @@ namespace Commands { {"refresh", PARAMS { return refreshGw(p); }}, {"line", PARAMS { return line(p); }}, {"settings", PARAMS { return settings(p); }}, - {"sam", PARAMS { return sam(p); }}, {"ins", PARAMS { return insertions(p); }}, {"insertions", PARAMS { return insertions(p); }}, {"mm", PARAMS { return mismatches(p); }}, {"mismatches", PARAMS { return mismatches(p); }}, {"edges", PARAMS { return edges(p); }}, {"soft_clips", PARAMS { return soft_clips(p); }}, - {"help", PARAMS { return getHelp(p, command, parts); }}, - {"man", PARAMS { return getHelp(p, command, parts); }}, + {"log2-cov", PARAMS { return log2_cov(p); }}, + {"expand-tracks", PARAMS { return expand_tracks(p); }}, + {"tlen-y", PARAMS { return tlen_y(p); }}, + {"sam", PARAMS { return sam(p, command, parts, out); }}, + {"h", PARAMS { return getHelp(p, command, parts, out); }}, + {"help", PARAMS { return getHelp(p, command, parts, out); }}, + {"man", PARAMS { return getHelp(p, command, parts, out); }}, {"link", PARAMS { return link(p, command, parts); }}, - {"count", PARAMS { return count(p, command); }}, - {"filter", PARAMS { return addFilter(p, command); }}, - {"tags", PARAMS { return tags(p, command); }}, - {"f", PARAMS { return findRead(p, parts); }}, - {"find", PARAMS { return findRead(p, parts); }}, - {"ylim", PARAMS { return setYlim(p, parts); }}, - {"indel-length", PARAMS { return indelLength(p, parts); }}, - {"remove", PARAMS { return remove(p, parts); }}, - {"cov", PARAMS { return cov(p, parts); }}, - + {"v", PARAMS { return var_info(p, command, parts, out); }}, + {"var", PARAMS { return var_info(p, command, parts, out); }}, + {"count", PARAMS { return count(p, command, out); }}, + {"filter", PARAMS { return addFilter(p, command, out); }}, + {"tags", PARAMS { return tags(p, command, out); }}, + {"mate", PARAMS { return mate(p, command, out); }}, + {"f", PARAMS { return findRead(p, parts, out); }}, + {"find", PARAMS { return findRead(p, parts, out); }}, + {"ylim", PARAMS { return setYlim(p, parts, out); }}, + {"indel-length", PARAMS { return indelLength(p, parts, out); }}, + {"rm", PARAMS { return remove(p, parts, out); }}, + {"remove", PARAMS { return remove(p, parts, out); }}, + {"cov", PARAMS { return cov(p, parts, out); }}, + {"theme", PARAMS { return theme(p, parts, out); }}, + {"goto", PARAMS { return goto_command(p, parts); }}, + {"grid", PARAMS { return grid(p, parts); }}, + {"add", PARAMS { return add_region(p, parts, out); }}, + {"s", PARAMS { return snapshot(p, parts, out); }}, + {"snapshot", PARAMS { return snapshot(p, parts, out); }}, + {"online", PARAMS { return online(p, parts, out); }}, + {"save", PARAMS { return online(p, parts, out); }}, }; auto it = functionMap.find(parts[0]); + Err res; if (it != functionMap.end()) { - std::cout << "Command success!\n"; - return it->second(p, command, parts); // Execute the mapped function + res = it->second(p, command, parts, out); // Execute the mapped function } else { - std::cout << "Command not found!\n"; - return false; + res = infer_region_or_feature(p, command, parts); } - p->inputText = ""; + handle_err(res, out); } - - } \ No newline at end of file diff --git a/src/plot_commands.h b/src/plot_commands.h index c122d46..6d44da4 100644 --- a/src/plot_commands.h +++ b/src/plot_commands.h @@ -9,6 +9,6 @@ namespace Commands { - bool run_command_map(Manager::GwPlot* p, std::string& command); + void run_command_map(Manager::GwPlot* p, std::string& command, std::ostream& out); } diff --git a/src/plot_controls.cpp b/src/plot_controls.cpp index 63acaff..ffcd31e 100644 --- a/src/plot_controls.cpp +++ b/src/plot_controls.cpp @@ -33,32 +33,8 @@ namespace Manager { TRACK = -3 }; - enum Errors { - NONE, - CHROM_NOT_IN_REFERENCE, - FEATURE_NOT_IN_TRACKS, - BAD_REGION, - OPTION_NOT_UNDERSTOOD, - GENERIC - }; - constexpr int DRAG_UNSET = -1000000; - void error_report(int err) { - std::cerr << termcolor::red << "Error:" << termcolor::reset; - if (err == CHROM_NOT_IN_REFERENCE) { - std::cerr << " loci not understood\n"; - } else if (err == FEATURE_NOT_IN_TRACKS) { - std::cerr << " loci not understood, or feature name not found in tracks\n"; - } else if (err == BAD_REGION) { - std::cerr << " region not understood\n"; - } else if (err == OPTION_NOT_UNDERSTOOD) { - std::cerr << " option not understood\n"; - } else if (err == GENERIC) { - std::cerr << " command not understood / invalid loci or feature name\n"; - } - } - struct TipBounds { int lower, upper; std::string cmd; @@ -93,6 +69,7 @@ namespace Manager { // keeps track of input commands. returning GLFW_KEY_UNKNOWN stops further processing of key codes int GwPlot::registerKey(GLFWwindow* wind, int key, int scancode, int action, int mods) { + std::ostream& out = (terminalOutput) ? std::cout : outStr; if (key == GLFW_KEY_LEFT_CONTROL || key == GLFW_KEY_LEFT_SUPER) { if (action == GLFW_PRESS || action == GLFW_REPEAT) { ctrlPress = true; @@ -156,7 +133,7 @@ namespace Manager { if (mode == SETTINGS) { return key; } - std::cout << std::endl; + out << std::endl; if (!commandHistory.empty()) { inputText = commandHistory.back(); } @@ -168,7 +145,7 @@ namespace Manager { variantFileSelection = (variantFileSelection < (int)variantTracks.size() - 1) ? variantFileSelection + 1 : variantFileSelection; } if (variantFileSelection != before) { - std::cout << termcolor::magenta << "\nFile " << termcolor::reset << variantTracks[variantFileSelection].path << "\n"; + out << termcolor::magenta << "\nFile " << termcolor::reset << variantTracks[variantFileSelection].path << "\n"; redraw = true; processed = false; imageCache.clear(); @@ -274,7 +251,7 @@ namespace Manager { redraw = true; processed = true; commandToolTipIndex = -1; - std::cout << "\n"; + out << "\n"; return key; } else if (key == GLFW_KEY_TAB) { if (mode != SETTINGS) { @@ -307,7 +284,7 @@ namespace Manager { processed = true; imageCache.clear(); commandToolTipIndex = -1; - std::cout << "\n"; + out << "\n"; return GLFW_KEY_ENTER; } inputText += " "; @@ -323,13 +300,13 @@ namespace Manager { commandIndex -= 1; inputText = commandHistory[commandIndex]; charIndex = (int)inputText.size(); - Term::clearLine(); + Term::clearLine(out); return key; } else if (key == GLFW_KEY_DOWN && commandIndex < (int)commandHistory.size() - 1) { commandIndex += 1; inputText = commandHistory[commandIndex]; charIndex = (int)inputText.size(); - Term::clearLine(); + Term::clearLine(out); return key; } } @@ -412,14 +389,15 @@ namespace Manager { } } if (key == GLFW_KEY_ENTER) { - std::cout << std::endl; + out << std::endl; } return key; } void GwPlot::removeBam(int index) { if (index >= (int) bams.size()) { - std::cerr << termcolor::red << "Error:" << termcolor::reset << " bam index is out of range. Use 0-based indexing\n"; + std::ostream& outerr = (terminalOutput) ? std::cerr : outStr; + outerr << termcolor::red << "Error:" << termcolor::reset << " bam index is out of range. Use 0-based indexing\n"; return; } collections.erase(std::remove_if(collections.begin(), collections.end(), [&index](const auto &col) { @@ -436,7 +414,8 @@ namespace Manager { void GwPlot::removeTrack(int index) { if (index >= (int)tracks.size()) { - std::cerr << termcolor::red << "Error:" << termcolor::reset << " track index is out of range. Use 0-based indexing\n"; + std::ostream& outerr = (terminalOutput) ? std::cerr : outStr; + outerr << termcolor::red << "Error:" << termcolor::reset << " track index is out of range. Use 0-based indexing\n"; return; } for (auto &rgn : regions) { @@ -463,7 +442,8 @@ namespace Manager { regions.erase(regions.begin() + index); } } else { - std::cerr << termcolor::red << "Error:" << termcolor::reset << " region index is out of range. Use 0-based indexing\n"; + std::ostream& outerr = (terminalOutput) ? std::cerr : outStr; + outerr << termcolor::red << "Error:" << termcolor::reset << " region index is out of range. Use 0-based indexing\n"; return; } collections.erase(std::remove_if(collections.begin(), collections.end(), [&index](const auto col) { @@ -475,7 +455,7 @@ namespace Manager { imageCache.clear(); } - void GwPlot::highlightQname() { // todo make this more efficient + void GwPlot::highlightQname() { for (auto &cl : collections) { for (auto &a: cl.readQueue) { if (bam_get_qname(a.delegate) == target_qname) { @@ -488,8 +468,7 @@ namespace Manager { } bool GwPlot::commandProcessed() { - // note setting valid = true sets redraw to true and processed to false, resulting in re-drawing and - // re-collecting of reads + // text commands are forwarded to run_command_map, menu inputs are handled elsewhere Utils::rtrim(inputText); if (charIndex >= (int)inputText.size()) { charIndex = (int)inputText.size() - 1; @@ -497,11 +476,6 @@ namespace Manager { if (inputText.empty()) { return false; } - bool valid = false; - int reason = NONE; - constexpr char delim = ' '; - constexpr char delim_q = '\''; - if (mode != SETTINGS) { commandHistory.push_back(inputText); commandIndex = (int)commandHistory.size(); @@ -515,457 +489,22 @@ namespace Manager { return false; } } - processText = false; // all command text will be processed below - - bool success = Commands::run_command_map(this, inputText); - if (!success) { - return false; - } - - if (inputText == "log2-cov") { - opts.log2_cov = !(opts.log2_cov); - redraw = true; - if (mode == SINGLE) { - processed = true; - for (auto &cl : collections) { - cl.skipDrawingReads = false; - cl.skipDrawingCoverage = false; - } - } else { - processed = false; - } - inputText = ""; - imageCache.clear(); - return true; - } - else if (inputText == "expand-tracks") { - opts.expand_tracks = !(opts.expand_tracks); - redraw = true; - if (mode == SINGLE) { - processed = true; - } else { - processed = false; - } - inputText = ""; - imageCache.clear(); - return true; - } else if (Utils::startsWith(inputText, "mate")) { - std::string mate; - Utils::parseMateLocation(selectedAlign, mate, target_qname); - if (mate.empty()) { - std::cerr << termcolor::red << "Error:" << termcolor::reset << " could not parse mate location\n"; - inputText = ""; - return true; - } - if (regionSelection >= 0 && regionSelection < (int) regions.size()) { - if (inputText == "mate") { - regions[regionSelection] = Utils::parseRegion(mate); - processed = false; - for (auto &cl: collections) { - if (cl.regionIdx == regionSelection) { - cl.region = ®ions[regionSelection]; - cl.readQueue.clear(); - cl.covArr.clear(); - cl.levelsStart.clear(); - cl.levelsEnd.clear(); - } - } - processBam(); - highlightQname(); - redraw = true; - if (mode == SINGLE) { - processed = true; - } else { - processed = false; - } - inputText = ""; - imageCache.clear(); - return true; - } else if (inputText == "mate add" && mode == SINGLE) { - regions.push_back(Utils::parseRegion(mate)); - fetchRefSeq(regions.back()); - processed = false; - processBam(); - highlightQname(); - redraw = true; - processed = true; - inputText = ""; - imageCache.clear(); - return true; - } - } - } else if (Utils::startsWith(inputText, "theme")) { - std::vector split = Utils::split(inputText, delim); - if (split.size() != 2) { - std::cerr << termcolor::red << "Error:" << termcolor::reset << " theme must be either 'igv', 'dark' or 'slate'\n"; - inputText = ""; - return true; - } - if (split.back() == "dark") { - opts.theme = Themes::DarkTheme(); opts.theme.setAlphas(); valid = true; imageCache.clear(); opts.theme_str = "dark"; - imageCache.clear(); - } else if (split.back() == "igv") { - opts.theme = Themes::IgvTheme(); opts.theme.setAlphas(); valid = true; imageCache.clear(); opts.theme_str = "igv"; - imageCache.clear(); - } else if (split.back() == "slate") { - opts.theme = Themes::SlateTheme(); opts.theme.setAlphas(); valid = true; imageCache.clear(); opts.theme_str = "slate"; - imageCache.clear(); - } else { - valid = false; - reason = OPTION_NOT_UNDERSTOOD; - error_report(reason); - } - } - else if (inputText == "tlen-y") { - if (!opts.tlen_yscale) { - opts.max_tlen = 2000; - opts.ylim = 2000; - samMaxY = 2000; - } else { - opts.ylim = 60; - samMaxY = 60; - } - opts.tlen_yscale = !opts.tlen_yscale; - std::cerr << opts.max_tlen << std::endl; - valid = true; - } else if (Utils::startsWith(inputText, "goto")) { - std::vector split = Utils::split(inputText, delim_q); - if (split.size() == 1) { - split = Utils::split(inputText, delim); - } - if (split.size() > 1 && split.size() < 4) { - int index = regionSelection; - Utils::Region rgn; - try { - rgn = Utils::parseRegion(split[1]); - int res = faidx_has_seq(fai, rgn.chrom.c_str()); - if (res <= 0) { - valid = false; - reason = CHROM_NOT_IN_REFERENCE; - } else { - valid = true; - } - } catch (...) { - valid = false; - reason = BAD_REGION; - } - if (valid) { - if (mode != SINGLE) { mode = SINGLE; } - if (regions.empty()) { - regions.push_back(rgn); - fetchRefSeq(regions.back()); - } else { - if (index < (int)regions.size()) { - if (regions[index].chrom == rgn.chrom) { - rgn.markerPos = regions[index].markerPos; - rgn.markerPosEnd = regions[index].markerPosEnd; - } - regions[index] = rgn; - fetchRefSeq(regions[index]); - } - } - } else { // search all tracks for matching name, slow but ok for small tracks - if (!tracks.empty()) { - bool res = HGW::searchTracks(tracks, split[1], rgn); - if (res) { - valid = true; - if (mode != SINGLE) { mode = SINGLE; } - if (regions.empty()) { - regions.push_back(rgn); - fetchRefSeq(regions.back()); - } else { - if (index < (int) regions.size()) { - regions[index] = rgn; - fetchRefSeq(regions[index]); - valid = true; - } - } - } else { - reason = GENERIC; - valid = false; - } - } - } - if (valid) { - redraw = true; - processed = false; - } else { - error_report(reason); - } - inputText = ""; - return true; - } - } else if (Utils::startsWith(inputText, "grid")) { - try { - std::vector split = Utils::split(inputText, ' '); - opts.number = Utils::parseDimensions(split[1]); - valid = true; - } catch (...) { - valid = false; - } - } else if (Utils::startsWith(inputText, "add") && mode != TILED) { - - if (mode != SINGLE) { mode = SINGLE; } - std::vector split = Utils::split(inputText, delim_q); - if (split.size() == 1) { - split = Utils::split(inputText, delim); - } - if (split.size() > 1) { - for (int i=1; i < (int)split.size(); ++i) { - try { - Utils::Region dummy_region = Utils::parseRegion(split[1]); - int res = faidx_has_seq(fai, dummy_region.chrom.c_str()); - if (res <= 0) { - valid = false; - reason = GENERIC; - if (inputText == "add mate") { - std::cerr << "Did you mean to use the 'mate add' function instead?\n"; - } - } else { - regions.push_back(dummy_region); - fetchRefSeq(regions.back()); - valid = true; - } - } catch (...) { - std::cerr << termcolor::red << "Error parsing :add" << termcolor::reset; - inputText = ""; - return true; - } - } - } else { - std::cerr << termcolor::red << "Error:" << termcolor::reset << " expected a Region e.g. chr1:1-20000\n"; - inputText = ""; - return true; - } - } else if (inputText == "v" || Utils::startsWith(inputText, "var") || Utils::startsWith(inputText, "v ")) { - if (variantTracks.empty()) { - inputText = ""; - return true; - } - currentVarTrack = &variantTracks[variantFileSelection]; - if (currentVarTrack->multiLabels.empty()) { - std::cerr << termcolor::red << "Error:" << termcolor::reset << " no variant loaded\n"; - inputText = ""; - processed = true; - redraw = false; - return true; - } else if (currentVarTrack->blockStart+mouseOverTileIndex >= (int)currentVarTrack->multiLabels.size() || mouseOverTileIndex == -1) { - inputText = ""; - processed = true; - redraw = false; - return true; - } - std::vector split = Utils::split(inputText, delim); - Utils::Label &lbl = currentVarTrack->multiLabels[currentVarTrack->blockStart + mouseOverTileIndex]; - Term::clearLine(); - if (currentVarTrack->type == HGW::TrackType::VCF) { - currentVarTrack->vcf.printTargetRecord(lbl.variantId, lbl.chrom, lbl.pos); - std::string variantStringCopy = currentVarTrack->vcf.variantString; - currentVarTrack->vcf.get_samples(); - std::vector sample_names_copy = currentVarTrack->vcf.sample_names; - if (variantStringCopy.empty()) { - std::cerr << termcolor::red << "Error:" << termcolor::reset << " could not parse vcf/bcf line"; - } else { - int requests = (int)split.size(); - if (requests == 1) { - Term::clearLine(); - std::cout << "\r" << variantStringCopy << std::endl; - } else { - std::string requestedVars; - std::vector vcfCols = Utils::split(variantStringCopy, '\t'); - for (int i = 1; i < requests; ++i) { - std::string result; - try { - Parse::parse_vcf_split(result, vcfCols, split[i], sample_names_copy); - } catch (...) { - std::cerr << termcolor::red << "Error:" << termcolor::reset << " could not parse " << split[i] << std::endl; - break; - } - if (i != requests-1) { - requestedVars += split[i]+": "+result+"\t"; - } else { - requestedVars += split[i]+": "+result; - } - } - if (!requestedVars.empty()) { - Term::clearLine(); - std::cout << "\r" << requestedVars << std::endl; - } - } - } - valid = true; - } else { - currentVarTrack->variantTrack.printTargetRecord(lbl.variantId, lbl.chrom, lbl.pos); - if (currentVarTrack->variantTrack.variantString.empty()) { - Term::clearLine(); - std::cout << "\r" << currentVarTrack->variantTrack.variantString << std::endl; - } else { - std::cerr << termcolor::red << "Error:" << termcolor::reset << " could not parse variant line"; - } - } - valid = true; - - } else if (inputText == "s" || Utils::startsWith(inputText, "snapshot") || Utils::startsWith(inputText, "s ")) { - std::vector split = Utils::split(inputText, delim); - if (split.size() > 2) { - valid = false; - } else { - std::string fname; - currentVarTrack = &variantTracks[variantFileSelection]; - if (split.size() == 1) { - if (mode == Show::SINGLE) { - std::filesystem::path fname_path = Utils::makeFilenameFromRegions(regions); -#if defined(_WIN32) || defined(_WIN64) - const wchar_t* pc = fname_path.filename().c_str(); - std::wstring ws(pc); - std::string p(ws.begin(), ws.end()); - fname = p; -#else - fname = fname_path.filename(); -#endif - } else if (currentVarTrack != nullptr) { - fname = "index_" + std::to_string(currentVarTrack->blockStart) + "_" + - std::to_string(opts.number.x * opts.number.y) + ".png"; - } - } else { - std::string nameFormat = split[1]; - if (currentVarTrack->type == HGW::TrackType::VCF && mode == Show::SINGLE) { - if (mouseOverTileIndex == -1 || currentVarTrack->blockStart + mouseOverTileIndex > (int) currentVarTrack->multiLabels.size()) { - inputText = ""; - redraw = true; - processed = false; - return true; - } - Utils::Label &lbl = currentVarTrack->multiLabels[currentVarTrack->blockStart + mouseOverTileIndex]; - currentVarTrack->vcf.get_samples(); - std::vector sample_names_copy = currentVarTrack->vcf.sample_names; - currentVarTrack->vcf.printTargetRecord(lbl.variantId, lbl.chrom, lbl.pos); - std::string variantStringCopy = currentVarTrack->vcf.variantString; - if (!variantStringCopy.empty() && variantStringCopy[variantStringCopy.length()-1] == '\n') { - variantStringCopy.erase(variantStringCopy.length()-1); - } - std::vector vcfCols = Utils::split(variantStringCopy, '\t'); - try { - Parse::parse_output_name_format(nameFormat, vcfCols, sample_names_copy, bam_paths,lbl.current()); - } catch (...) { - std::cerr << termcolor::red << "Error:" << termcolor::reset - << " could not parse " << nameFormat << std::endl; - inputText = ""; - redraw = true; - processed = false; - return true; - } - } - fname = nameFormat; - Utils::trim(fname); - } - std::filesystem::path outdir = opts.outdir; - std::filesystem::path fname_path(fname); - std::filesystem::path out_path = outdir / fname_path; - if (!std::filesystem::exists(out_path.parent_path()) && !out_path.parent_path().empty()) { - std::cerr << termcolor::red << "Error:" << termcolor::reset << " path not found " << out_path.parent_path() << std::endl; - } else { - if (!imageCacheQueue.empty()) { - Manager::imagePngToFile(imageCacheQueue.back().second, out_path.string()); - Term::clearLine(); - std::cout << "\rSaved to " << out_path << std::endl; - } - } - valid = true; - } - } else if (Utils::startsWith(inputText, "online")) { - if (regions.empty()) { - std::cerr << termcolor::red << "Error:" << termcolor::reset << " please navigate to a region first" << std::endl; - redraw = false; - processed = true; - inputText = ""; - return false; - } - std::vector split = Utils::split(inputText, delim); - std::string genome_tag; - if (opts.genome_tag.empty() && split.size() >= 2) { - genome_tag = split[1]; - } else { - genome_tag = opts.genome_tag; - } - Term::printOnlineLinks(tracks, regions[regionSelection], genome_tag); - redraw = false; - processed = true; - inputText = ""; - return true; - - } else { - Utils::Region rgn; - try { - rgn = Utils::parseRegion(inputText); - valid = true; - } catch (...) { - valid = false; - reason = BAD_REGION; - } - if (valid) { - int res = faidx_has_seq(fai, rgn.chrom.c_str()); - if (res <= 0) { - valid = false; - reason = GENERIC; - } - } - if (valid) { - if (mode != SINGLE) { mode = SINGLE; } - if (regions.empty()) { - regions.push_back(rgn); - fetchRefSeq(regions.back()); - } else { - if (regions[regionSelection].chrom == rgn.chrom) { - rgn.markerPos = regions[regionSelection].markerPos; - rgn.markerPosEnd = regions[regionSelection].markerPosEnd; - } - regions[regionSelection] = rgn; - fetchRefSeq(regions[regionSelection]); - } - } else { // search all tracks for matching name, slow but ok for small tracks - if (!tracks.empty()) { - bool res = HGW::searchTracks(tracks, inputText, rgn); - if (res) { - valid = true; - reason = NONE; - if (mode != SINGLE) { mode = SINGLE; } - if (regions.empty()) { - regions.push_back(rgn); - fetchRefSeq(regions.back()); - } else { - if (regionSelection < (int) regions.size()) { - regions[regionSelection] = rgn; - fetchRefSeq(regions[regionSelection]); - } - } - } else { - valid = false; - reason = GENERIC; - } - } - } - } - if (valid) { - redraw = true; - processed = false; - } else { - std::cout << inputText << "\n\n"; -// error_report(reason); - } - inputText = ""; + processText = false; // text will be processed by run_command_map + std::ostream& out = (terminalOutput) ? std::cout : outStr; + Commands::run_command_map(this, inputText, out); return true; } void GwPlot::printIndexInfo() { + std::ostream& out = (terminalOutput) ? std::cout : outStr; int term_width = Utils::get_terminal_width() - 1; - Term::clearLine(); + Term::clearLine(out); std::string i_str = "\rIndex "; if (term_width <= (int)i_str.size()) { return; } - Term::clearLine(); - std::cout << termcolor::bold << i_str << termcolor::reset; + Term::clearLine(out); + out << termcolor::bold << i_str << termcolor::reset; term_width -= (int)i_str.size(); int blockStart = currentVarTrack->blockStart; @@ -977,14 +516,14 @@ namespace Manager { } if (term_width <= (int)ind.size()) { - std::cout << std::flush; + out << std::flush; return; } - std::cout << ind; + out << ind; term_width -= (int)ind.size(); if ((int)currentVarTrack->multiRegions.size() <= blockStart) { - std::cout << std::flush; + out << std::flush; return; } Utils::Region &start_region = currentVarTrack->multiRegions[blockStart].front(); @@ -993,14 +532,14 @@ namespace Manager { std::string region_str1 = " " + chrom + ":" + std::to_string(start); if (term_width <= (int)region_str1.size()) { - std::cout << std::flush; + out << std::flush; return; } - std::cout << region_str1; + out << region_str1; term_width -= (int)region_str1.size(); if ((int)currentVarTrack->multiRegions.size() <= blockStart + (opts.number.x * opts.number.y) - 1) { - std::cout << std::flush; + out << std::flush; return; } @@ -1008,44 +547,45 @@ namespace Manager { int end = end_region.end; std::string region_str2 = + "-" + std::to_string(end); if (term_width <= (int)region_str2.size()) { - std::cout << std::flush; + out << std::flush; return; } - std::cout << region_str2; - std::cout << std::flush; + out << region_str2; + out << std::flush; } int GwPlot::printRegionInfo() { + std::ostream& out = (terminalOutput) ? std::cout : outStr; int term_width = Utils::get_terminal_width() - 1; if (regions.empty()) { - std::cout << "\r" << std::flush; + out << "\r" << std::flush; return term_width; } std::string pos_str = "\rPos "; if (term_width <= (int)pos_str.size()) { return term_width; } - Term::clearLine(); - std::cout << termcolor::bold << pos_str << termcolor::reset; + Term::clearLine(out); + out << termcolor::bold << pos_str << termcolor::reset; term_width -= (int)pos_str.size(); auto r = regions[regionSelection]; std::string region_str = r.chrom + ":" + std::to_string(r.start + 1) + "-" + std::to_string(r.end + 1); if (term_width <= (int)region_str.size()) { - std::cout << std::flush; + out << std::flush; return term_width; } - std::cout << termcolor::cyan << region_str << termcolor::white; + out << termcolor::cyan << region_str << termcolor::white; term_width -= (int)region_str.size(); std::string size_str = " (" + Utils::getSize(r.end - r.start) + ")"; if (term_width <= (int)size_str.size()) { - std::cout << std::flush; + out << std::flush; return term_width; } - std::cout << size_str; + out << size_str; term_width -= (int)size_str.size(); - std::cout << termcolor::reset << std::flush; + out << termcolor::reset << std::flush; return term_width; } @@ -1061,6 +601,7 @@ namespace Manager { //fonts.setTypeface(opts.font_str, opts.font_size); 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) { @@ -1069,9 +610,9 @@ namespace Manager { for (auto &bm: bams) { hts_set_fai_filename(bm, reference.c_str()); } - std::cerr << termcolor::bold << "\n" << opts.genome_tag << termcolor::reset << " loaded from " << reference << std::endl; + outerr << termcolor::bold << "\n" << opts.genome_tag << termcolor::reset << " loaded from " << reference << std::endl; } else { - std::cerr << termcolor::red << "Error:" << termcolor::reset << " could not open " << opts.myIni["genomes"][opts.genome_tag].c_str() << std::endl; + outerr << termcolor::red << "Error:" << termcolor::reset << " could not open " << opts.myIni["genomes"][opts.genome_tag].c_str() << std::endl; } fai_destroy(fai_test); } @@ -1079,7 +620,7 @@ namespace Manager { 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)) { - std::cerr << "Warning: track file does not exists - " << trk_item << std::endl; + outerr << "Warning: track file does not exists - " << trk_item << std::endl; } else { bool already_loaded = false; for (const auto ¤t_track : tracks) { @@ -1117,6 +658,7 @@ namespace Manager { // Of note, mouseButton events may be translated into keyPress events and processed here // For example, clicking on a commands from the menu pop-up will translate into a keyPress ENTER and // processed using registerKey + std::ostream& out = (terminalOutput) ? std::cout : outStr; key = registerKey(window, key, scancode, action, mods); if (key == GLFW_KEY_UNKNOWN || captureText) { return; @@ -1333,7 +875,7 @@ namespace Manager { if (regionSelection >= (int)regions.size()) { regionSelection = 0; } - std::cout << "\nRegion " << regionSelection << std::endl; + out << "\nRegion " << regionSelection << std::endl; regionSelectionTriggered = true; regionTimer = std::chrono::high_resolution_clock::now(); } else if (key == opts.previous_region_view) { @@ -1342,7 +884,7 @@ namespace Manager { if (regionSelection < 0) { regionSelection = (int)regions.size() - 1; } - std::cout << "\nRegion " << regionSelection << std::endl; + out << "\nRegion " << regionSelection << std::endl; regionSelectionTriggered = true; regionTimer = std::chrono::high_resolution_clock::now(); } else if (key == opts.scroll_down) { @@ -1437,7 +979,7 @@ namespace Manager { opts.link_op += 1; } std::string lk = (opts.link_op > 0) ? ((opts.link_op == 1) ? "sv" : "all") : "none"; - std::cout << "\nLinking selection " << lk << std::endl; + out << "\nLinking selection " << lk << std::endl; imageCache.clear(); HGW::refreshLinked(collections, opts, &samMaxY); redraw = true; @@ -1445,11 +987,12 @@ namespace Manager { } void GwPlot::addTrack(std::string &path, bool print_message=true) { + std::ostream& out = (terminalOutput) ? std::cout : outStr; bool good = false; if (Utils::endsWith(path, ".bam") || Utils::endsWith(path, ".cram")) { good = true; if (print_message) { - std::cout << termcolor::magenta << "\nAlignments " << termcolor::reset << path << "\n"; + out << termcolor::magenta << "\nAlignments " << termcolor::reset << path << "\n"; } bam_paths.push_back(path); htsFile* f = sam_open(path.c_str(), "r"); @@ -1472,8 +1015,7 @@ namespace Manager { currentVarTrack->blockStart = 0; mode = Manager::Show::TILED; if (print_message) { - std::cout << 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()); @@ -1481,7 +1023,7 @@ namespace Manager { tracks.back().open(path, true); tracks.back().variant_distance = &opts.variant_distance; if (print_message) { - std::cout << termcolor::magenta << "\nTrack " << termcolor::reset << path << "\n"; + out << termcolor::magenta << "\nTrack " << termcolor::reset << path << "\n"; } } catch (...) { tracks.pop_back(); @@ -1587,6 +1129,7 @@ namespace Manager { } void GwPlot::mouseButton(int button, int action, int mods) { + std::ostream& out = (terminalOutput) ? std::cout : outStr; GLFWwindow* wind = window; double x, y; glfwGetCursorPos(window, &x, &y); @@ -1677,13 +1220,13 @@ namespace Manager { if (collections.empty()) { float xScaling = (float)((regionWidth - gap - gap) / ((double)(regions[regionSelection].end -regions[regionSelection].start))); float xOffset = (regionWidth * (float)regionSelection) + gap; - Term::printRefSeq(®ions[regionSelection], xW, xOffset, xScaling); + Term::printRefSeq(®ions[regionSelection], xW, xOffset, xScaling, out); } else { for (auto &cl: collections) { float min_x = cl.xOffset; float max_x = cl.xScaling * ((float)(cl.region->end - cl.region->start)) + min_x; if (xW > min_x && xW < max_x) { - Term::printRefSeq(cl.region, xW, cl.xOffset, cl.xScaling); + Term::printRefSeq(cl.region, xW, cl.xOffset, cl.xScaling, out); break; } } @@ -1708,7 +1251,7 @@ namespace Manager { float step_track = (stepY) / ((float)regions[regionSelection].featureLevels[trackIdx]); float y = fb_height - totalTabixY - refSpace; // start of tracks on canvas int featureLevel = (int)(yW - y - (trackIdx * stepY)) / step_track; - Term::printTrack(relX, targetTrack, ®ions[tIdx], false, featureLevel, trackIdx, target_qname, &target_pos); + Term::printTrack(relX, targetTrack, ®ions[tIdx], false, featureLevel, trackIdx, target_qname, &target_pos, out); } } clickedIdx = -1; @@ -1754,7 +1297,7 @@ namespace Manager { if (!opts.tlen_yscale) { level = (int)((yW - (float) cl.yOffset) / yScaling); if (level < 0) { // print coverage info (mouse Pos functions already prints out cov info to console) - std::cout << std::endl; + out << std::endl; return; } if (cl.vScroll < 0) { @@ -1765,7 +1308,6 @@ namespace Manager { level = (int) ((yW - (float) cl.yOffset) / (((trackY - gap) * 0.95) / (float)(max_bound))); slop = (int)(max_bound * 0.025); slop = (slop <= 0) ? 25 : slop; - std::cerr << level << std::endl; } std::vector::iterator bnd; bnd = std::lower_bound(cl.readQueue.begin(), cl.readQueue.end(), pos, @@ -1775,7 +1317,7 @@ namespace Manager { if (bnd->y == level && (int)bnd->pos <= pos && pos < (int)bnd->reference_end) { bnd->edge_type = 4; target_qname = bam_get_qname(bnd->delegate); - Term::printRead(bnd, headers[cl.bamIdx], selectedAlign, cl.region->refSeq, cl.region->start, cl.region->end, opts.low_memory); + Term::printRead(bnd, headers[cl.bamIdx], selectedAlign, cl.region->refSeq, cl.region->start, cl.region->end, opts.low_memory, out); redraw = true; processed = true; cl.skipDrawingReads = false; @@ -1786,7 +1328,7 @@ namespace Manager { if ((bnd->y >= level - slop && bnd->y < level) && (int)bnd->pos <= pos && pos < (int)bnd->reference_end) { bnd->edge_type = 4; target_qname = bam_get_qname(bnd->delegate); - Term::printRead(bnd, headers[cl.bamIdx], selectedAlign, cl.region->refSeq, cl.region->start, cl.region->end, opts.low_memory); + Term::printRead(bnd, headers[cl.bamIdx], selectedAlign, cl.region->refSeq, cl.region->start, cl.region->end, opts.low_memory, out); redraw = true; processed = true; cl.skipDrawingReads = false; @@ -1863,7 +1405,7 @@ namespace Manager { if (currentVarTrack->type == HGW::TrackType::IMAGES) { currentVarTrack->multiRegions.clear(); } - std::cout << std::endl; + out << std::endl; } } else if (mode == Manager::TILED) { currentVarTrack = &variantTracks[variantFileSelection]; @@ -2027,6 +1569,7 @@ namespace Manager { if (regions.empty() || mode == TILED) { return; } + std::ostream& out = (terminalOutput) ? std::cout : outStr; int pos = ((int) (((double)xPos - (double)xOffset) / (double)xScaling)) + region->start; std::string s = Term::intToStringCommas(pos); int term_width_remaining = printRegionInfo(); @@ -2034,19 +1577,20 @@ namespace Manager { if (term_width_remaining < (int)s.size()) { return; } - std::cout << s << std::flush; + out << s << std::flush; if (!bam_paths.empty()) { term_width_remaining -= (int)s.size(); std::string base_filename = " - " + bam_paths[bamIdx].substr(bam_paths[bamIdx].find_last_of("/\\") + 1); if (term_width_remaining < (int)base_filename.size()) { - std::cout << std::flush; + out << std::flush; return; } - std::cout << base_filename << std::flush; + out << base_filename << std::flush; } } void GwPlot::mousePos(double xPos, double yPos) { + std::ostream& out = (terminalOutput) ? std::cout : outStr; GLFWwindow* wind = window; int windX, windY; glfwGetWindowSize(wind, &windX, &windY); @@ -2254,7 +1798,7 @@ namespace Manager { 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; - Term::printTrack(relX, targetTrack, ®ions[tIdx], true, featureLevel, targetIndex, target_qname, &target_pos); + Term::printTrack(relX, targetTrack, ®ions[tIdx], true, featureLevel, targetIndex, target_qname, &target_pos, out); } } if (rs < 0) { // print reference info @@ -2262,13 +1806,13 @@ namespace Manager { float xOffset = (regionWidth * (float)regionSelection) + gap; if (rs == REFERENCE_TRACK) { if (collections.empty()) { - Term::updateRefGenomeSeq(®ions[regionSelection], (float)xPos_fb, xOffset, xScaling); + Term::updateRefGenomeSeq(®ions[regionSelection], (float)xPos_fb, xOffset, xScaling, out); } else { for (auto &cl: collections) { float min_x = cl.xOffset; float max_x = cl.xScaling * ((float)(cl.region->end - cl.region->start)) + min_x; if (xPos_fb > min_x && xPos_fb < max_x) { - Term::updateRefGenomeSeq(cl.region, (float)xPos_fb, cl.xOffset, cl.xScaling); + Term::updateRefGenomeSeq(cl.region, (float)xPos_fb, cl.xOffset, cl.xScaling, out); break; } } @@ -2285,8 +1829,8 @@ namespace Manager { float f_level = ((yPos_fb - (float) cl.yOffset) / (trackY / (float)(cl.levelsStart.size() - cl.vScroll ))); int level = (f_level < 0) ? -1 : (int)(f_level); if (level < 0 && cl.region->end - cl.region->start < 50000) { - Term::clearLine(); - Term::printCoverage(pos, cl); + Term::clearLine(out); + Term::printCoverage(pos, cl, out); return; } updateCursorGenomePos(cl.xOffset, cl.xScaling, (float)xPos_fb, cl.region, cl.bamIdx); @@ -2308,14 +1852,14 @@ namespace Manager { } Utils::Label *label = ¤tVarTrack->multiLabels[currentVarTrack->blockStart + i]; label->mouseOver = true; - Term::printVariantFileInfo(label, mouseOverTileIndex + currentVarTrack->blockStart); + Term::printVariantFileInfo(label, mouseOverTileIndex + currentVarTrack->blockStart, out); } else if (currentVarTrack->blockStart + i < (int)currentVarTrack->image_glob.size()) { if (i != mouseOverTileIndex) { mouseOverTileIndex = i; } Utils::Label *label = ¤tVarTrack->multiLabels[currentVarTrack->blockStart + i]; label->mouseOver = true; - Term::printVariantFileInfo(label, mouseOverTileIndex + currentVarTrack->blockStart); + Term::printVariantFileInfo(label, mouseOverTileIndex + currentVarTrack->blockStart, out); } } else if (mode == SETTINGS) { Menu::menuMousePos(opts, fonts, (float)xPos_fb, (float)yPos_fb, (float)fb_height, (float)fb_width, &redraw); diff --git a/src/plot_manager.cpp b/src/plot_manager.cpp index 63c8209..d792893 100644 --- a/src/plot_manager.cpp +++ b/src/plot_manager.cpp @@ -48,16 +48,16 @@ namespace Manager { void HiddenWindow::init(int width, int height) { if (!glfwInit()) { - std::cerr<<"ERROR: could not initialize GLFW3"<data(), 1, png->size(), fout); diff --git a/src/plot_manager.h b/src/plot_manager.h index 4d27648..dec1360 100644 --- a/src/plot_manager.h +++ b/src/plot_manager.h @@ -86,6 +86,9 @@ namespace Manager { bool processed; bool drawLine; + bool terminalOutput; // recoverable runtime errors and output sent to terminal or outStr + std::ostringstream outStr; + std::vector pixelMemory; std::string reference; @@ -110,6 +113,9 @@ namespace Manager { std::vector variantTracks; // make image tiles from these + HGW::GwVariantTrack *currentVarTrack; // var track with current focus/event + int mouseOverTileIndex; // tile with mouse over + std::vector filters; ankerl::unordered_dense::map< int, sk_sp> imageCache; @@ -225,7 +231,6 @@ namespace Manager { bool tabBorderPress; std::vector< std::string > commandHistory; int commandIndex, charIndex; - int mouseOverTileIndex; float totalCovY, covY, totalTabixY, tabixY, trackY, regionWidth, bamHeight, refSpace, sliderSpace; @@ -244,8 +249,6 @@ namespace Manager { int clickedIdx; int commandToolTipIndex; - HGW::GwVariantTrack *currentVarTrack; - std::vector bboxes; BS::thread_pool pool; diff --git a/src/term_out.cpp b/src/term_out.cpp index db569f6..8fe3a07 100644 --- a/src/term_out.cpp +++ b/src/term_out.cpp @@ -28,87 +28,81 @@ namespace Term { - void help(Themes::IniOptions &opts) { - std::cout << termcolor::italic << "\n* Enter a command by selecting the GW window (not the terminal) and type '/' or ':' plus [COMMAND]' *\n" << termcolor::reset; - std::cout << termcolor::italic << "\n* For a detailed manual type ':man [COMMAND]' *\n" << termcolor::reset; - std::cout << termcolor::underline << "\nCommand Modifier Description \n" << termcolor::reset; - std::cout << termcolor::green << "[locus] " << termcolor::reset << "e.g. 'chr1' or 'chr1:1-20000'\n"; - std::cout << termcolor::green << "add region(s) " << termcolor::reset << "Add one or more regions e.g. 'add chr1:1-20000'\n"; -// std::cout << termcolor::green << "config " << termcolor::reset << "Opens .gw.ini config in a text editor\n"; - std::cout << termcolor::green << "count expression? " << termcolor::reset << "Count reads. See filter for example expressions'\n"; - std::cout << termcolor::green << "cov value? " << termcolor::reset << "Change max coverage value. Use 'cov' to toggle coverage\n"; - std::cout << termcolor::green << "edges " << termcolor::reset << "Toggle edges\n"; - std::cout << termcolor::green << "expand-track " << termcolor::reset << "Toggle showing expanded tracks\n"; - std::cout << termcolor::green << "filter expression " << termcolor::reset << "Examples 'filter mapq > 0', 'filter ~flag & secondary'\n 'filter mapq >= 30 or seq-len > 100'\n"; - std::cout << termcolor::green << "find, f qname? " << termcolor::reset << "To find other alignments from selected read use 'find'\n Or use 'find [QNAME]' to find target read'\n"; - std::cout << termcolor::green << "goto loci/feature " << termcolor::reset << "e.g. 'goto chr1:1-20000'. 'goto hTERT' \n"; - std::cout << termcolor::green << "grid width x height " << termcolor::reset << "Set the grid size for --variant images 'grid 8x8' \n"; - std::cout << termcolor::green << "indel-length int " << termcolor::reset << "Label indels >= length\n"; - std::cout << termcolor::green << "insertions, ins " << termcolor::reset << "Toggle insertions\n"; - std::cout << termcolor::green << "line " << termcolor::reset << "Toggle mouse position vertical line\n"; - std::cout << termcolor::green << "link none/sv/all " << termcolor::reset << "Switch read-linking 'link all'\n"; - std::cout << termcolor::green << "log2-cov " << termcolor::reset << "Toggle scale coverage by log2\n"; - std::cout << 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"; - std::cout << termcolor::green << "mismatches, mm " << termcolor::reset << "Toggle mismatches\n"; - std::cout << termcolor::green << "online " << termcolor::reset << "Show links to online genome browsers\n"; - std::cout << termcolor::green << "quit, q - " << termcolor::reset << "Quit GW\n"; - std::cout << termcolor::green << "refresh, r - " << termcolor::reset << "Refresh and re-draw the window\n"; - std::cout << 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"; - std::cout << termcolor::green << "sam " << termcolor::reset << "Print selected read in sam format\n"; - std::cout << termcolor::green << "settings " << termcolor::reset << "Open the settings menu'\n"; - std::cout << 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"; - std::cout << termcolor::green << "soft-clips, sc " << termcolor::reset << "Toggle soft-clips\n"; - std::cout << termcolor::green << "tags " << termcolor::reset << "Print selected sam tags\n"; - std::cout << termcolor::green << "theme igv/dark/slate " << termcolor::reset << "Switch color theme e.g. 'theme dark'\n"; - std::cout << termcolor::green << "tlen-y " << termcolor::reset << "Toggle --tlen-y option\n"; -// std::cout << termcolor::green << "toggle cov " << termcolor::reset << "Toggle visibility of feature\n"; -// std::cout << termcolor::green << " edges " << termcolor::reset << "\n"; -// std::cout << termcolor::green << " insertions, ins " << termcolor::reset << "\n"; -// std::cout << termcolor::green << " line " << termcolor::reset << "\n"; -// std::cout << termcolor::green << " log2-cov " << termcolor::reset << "\n"; -// std::cout << termcolor::green << " mismatches, mm " << termcolor::reset << "\n"; -// std::cout << termcolor::green << " soft-clip, sc " << termcolor::reset << "\n"; - std::cout << termcolor::green << "var, v vcf_column? " << termcolor::reset << "Print variant information e.g. 'var', 'var info',\n or a list of columns 'var pos qual format.SU'\n"; - std::cout << termcolor::green << "ylim number " << termcolor::reset << "The maximum y-limit for the image e.g. 'ylim 100'\n"; - - std::cout << termcolor::underline << "\nHot keys \n" << termcolor::reset; - std::cout << "scroll left " << termcolor::bright_yellow; Term::printKeyFromValue(opts.scroll_left); std::cout << "\n" << termcolor::reset; - std::cout << "scroll right " << termcolor::bright_yellow; Term::printKeyFromValue(opts.scroll_right); std::cout << "\n" << termcolor::reset; - std::cout << "scroll down " << termcolor::bright_yellow; Term::printKeyFromValue(opts.scroll_down); std::cout << "\n" << termcolor::reset; - std::cout << "scroll up " << termcolor::bright_yellow; Term::printKeyFromValue(opts.scroll_up); std::cout << "\n" << termcolor::reset; - std::cout << "zoom in " << termcolor::bright_yellow; Term::printKeyFromValue(opts.zoom_in); std::cout << "\n" << termcolor::reset; - std::cout << "zoom out " << termcolor::bright_yellow; Term::printKeyFromValue(opts.zoom_out); std::cout << "\n" << termcolor::reset; - std::cout << "zoom to cursor " << termcolor::bright_yellow; std::cout << "CTRL + LEFT_MOUSE" << "\n" << termcolor::reset; - std::cout << "next region view " << termcolor::bright_yellow; Term::printKeyFromValue(opts.next_region_view); std::cout << "\n" << termcolor::reset; - std::cout << "previous region view " << termcolor::bright_yellow; Term::printKeyFromValue(opts.previous_region_view); std::cout << "\n" << termcolor::reset; - std::cout << "cycle link mode " << termcolor::bright_yellow; Term::printKeyFromValue(opts.cycle_link_mode); std::cout << "\n" << termcolor::reset; - std::cout << "find all alignments " << termcolor::bright_yellow; Term::printKeyFromValue(opts.find_alignments); std::cout << "\n" << termcolor::reset; - std::cout << "repeat last command " << termcolor::bright_yellow; std::cout << "ENTER" << "\n" << termcolor::reset; - std::cout << "resize window " << termcolor::bright_yellow; std::cout << "SHIFT + ARROW_KEY" << "\n" << termcolor::reset; - std::cout << "switch viewing mode " << termcolor::bright_yellow; std::cout << "TAB" << "\n" << termcolor::reset; - - std::cout << "\n"; + void help(Themes::IniOptions &opts, std::ostream& out) { + out << termcolor::italic << "\n* Select the GW window (not the terminal) and type '/' or ':' to enter a command\n" << termcolor::reset; + out << termcolor::italic << "\n* Get more help using 'man [COMMAND]' or 'help [COMMAND]'\n" << termcolor::reset; + out << termcolor::underline << "\nCommand Modifier Description \n" << termcolor::reset; + out << termcolor::green << "[locus] " << termcolor::reset << "e.g. 'chr1' or 'chr1:1-20000'\n"; + out << termcolor::green << "add region(s) " << termcolor::reset << "Add one or more regions e.g. 'add chr1:1-20000'\n"; +// out << termcolor::green << "config " << termcolor::reset << "Opens .gw.ini config in a text editor\n"; + out << termcolor::green << "count expression? " << termcolor::reset << "Count reads. See filter for example expressions'\n"; + out << termcolor::green << "cov value? " << termcolor::reset << "Change max coverage value. Use 'cov' to toggle coverage\n"; + out << termcolor::green << "edges " << termcolor::reset << "Toggle edges\n"; + out << termcolor::green << "expand-track " << termcolor::reset << "Toggle showing expanded tracks\n"; + out << termcolor::green << "filter expression " << termcolor::reset << "Examples 'filter mapq > 0', 'filter ~flag & secondary'\n 'filter mapq >= 30 or seq-len > 100'\n"; + out << termcolor::green << "find, f qname? " << termcolor::reset << "To find other alignments from selected read use 'find'\n Or use 'find [QNAME]' to find target read'\n"; + out << termcolor::green << "goto loci/feature " << termcolor::reset << "e.g. 'goto chr1:1-20000'. 'goto hTERT' \n"; + out << termcolor::green << "grid width x height " << termcolor::reset << "Set the grid size for --variant images 'grid 8x8' \n"; + out << termcolor::green << "indel-length int " << termcolor::reset << "Label indels >= length\n"; + 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 << "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"; + out << termcolor::green << "online " << termcolor::reset << "Show links to online genome browsers\n"; + out << termcolor::green << "quit, q - " << termcolor::reset << "Quit GW\n"; + out << termcolor::green << "refresh, r - " << termcolor::reset << "Refresh and re-draw the window\n"; + 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 << "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 (.xml) to file\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"; + out << termcolor::green << "tags " << termcolor::reset << "Print selected sam tags\n"; + out << termcolor::green << "theme igv/dark/slate " << termcolor::reset << "Switch color theme e.g. 'theme dark'\n"; + out << termcolor::green << "tlen-y " << termcolor::reset << "Toggle --tlen-y option\n"; + out << termcolor::green << "var, v vcf_column? " << termcolor::reset << "Print variant information e.g. 'var', 'var info',\n or a list of columns 'var pos qual format.SU'\n"; + out << termcolor::green << "ylim number " << termcolor::reset << "The maximum y-limit for the image e.g. 'ylim 100'\n"; + + out << termcolor::underline << "\nHot keys \n" << termcolor::reset; + out << "scroll left " << termcolor::bright_yellow; Term::printKeyFromValue(opts.scroll_left, out); out << "\n" << termcolor::reset; + out << "scroll right " << termcolor::bright_yellow; Term::printKeyFromValue(opts.scroll_right, out); out << "\n" << termcolor::reset; + out << "scroll down " << termcolor::bright_yellow; Term::printKeyFromValue(opts.scroll_down, out); out << "\n" << termcolor::reset; + out << "scroll up " << termcolor::bright_yellow; Term::printKeyFromValue(opts.scroll_up, out); out << "\n" << termcolor::reset; + out << "zoom in " << termcolor::bright_yellow; Term::printKeyFromValue(opts.zoom_in, out); out << "\n" << termcolor::reset; + out << "zoom out " << termcolor::bright_yellow; Term::printKeyFromValue(opts.zoom_out, out); out << "\n" << termcolor::reset; + out << "zoom to cursor " << termcolor::bright_yellow; out << "CTRL + LEFT_MOUSE" << "\n" << termcolor::reset; + out << "next region view " << termcolor::bright_yellow; Term::printKeyFromValue(opts.next_region_view, out); out << "\n" << termcolor::reset; + out << "previous region view " << termcolor::bright_yellow; Term::printKeyFromValue(opts.previous_region_view, out); out << "\n" << termcolor::reset; + out << "cycle link mode " << termcolor::bright_yellow; Term::printKeyFromValue(opts.cycle_link_mode, out); out << "\n" << termcolor::reset; + out << "find all alignments " << termcolor::bright_yellow; Term::printKeyFromValue(opts.find_alignments, out); out << "\n" << termcolor::reset; + out << "repeat last command " << termcolor::bright_yellow; out << "ENTER" << "\n" << termcolor::reset; + out << "resize window " << termcolor::bright_yellow; out << "SHIFT + ARROW_KEY" << "\n" << termcolor::reset; + out << "switch viewing mode " << termcolor::bright_yellow; out << "TAB" << "\n" << termcolor::reset; + + out << "\n"; } - void manuals(std::string &s) { - std::cout << "\nManual for command '" << s << "'\n"; - std::cout << "--------------------"; for (int i=0; i<(int)s.size(); ++i) std::cout << "-"; std::cout << "-\n\n"; + void manuals(std::string &s, std::ostream& out) { + out << "\nManual for command '" << s << "'\n"; + out << "--------------------"; for (int i=0; i<(int)s.size(); ++i) out << "-"; out << "-\n\n"; if (s == "[locus]" || s == "locus") { - std::cout << " Navigate to a genomic locus.\n You can use chromosome names or chromosome coordinates.\n Examples:\n 'chr1:1-20000', 'chr1', 'chr1:10000'\n\n"; + out << " Navigate to a genomic locus.\n You can use chromosome names or chromosome coordinates.\n Examples:\n 'chr1:1-20000', 'chr1', 'chr1:10000'\n\n"; } else if (s == "add") { - std::cout << " Add a genomic locus.\n This will add a new locus to the right-hand-side of your view.\n Examples:\n 'add chr1:1-20000', 'add chr2'\n\n"; + out << " Add a genomic locus.\n This will add a new locus to the right-hand-side of your view.\n Examples:\n 'add chr1:1-20000', 'add chr2'\n\n"; // } else if (s == "config") { -// std::cout << " Open the GW config file.\n The config file will be opened in a text editor, for Mac TextEdit will be used, linux will be vi, and windows will be notepad.\n\n"; +// out << " Open the GW config file.\n The config file will be opened in a text editor, for Mac TextEdit will be used, linux will be vi, and windows will be notepad.\n\n"; } else if (s == "count") { - std::cout << " Count the visible reads in each view.\n A summary output will be displayed for each view on the screen.\n Optionally a filter expression may be added to the command. See the man page for 'filter' for mote details\n Examples:\n 'count', 'count flag & 2', 'count flag & proper-pair' \n\n"; + out << " Count the visible reads in each view.\n A summary output will be displayed for each view on the screen.\n Optionally a filter expression may be added to the command. See the man page for 'filter' for mote details\n Examples:\n 'count', 'count flag & 2', 'count flag & proper-pair' \n\n"; } else if (s == "cov") { - std::cout << " Toggle coverage track.\n This will turn on/off coverage tracks.\n\n"; + out << " Toggle coverage track.\n This will turn on/off coverage tracks.\n\n"; } else if (s == "edges" || s == "sc") { - std::cout << " Toggle edge highlights.\n Edge highlights are turned on or off.\n\n"; + out << " Toggle edge highlights.\n Edge highlights are turned on or off.\n\n"; } else if (s == "expand-tracks") { - std::cout << " Toggle expand-tracks.\n Features in the tracks panel are expanded so overlapping features can be seen.\n\n"; + out << " Toggle expand-tracks.\n Features in the tracks panel are expanded so overlapping features can be seen.\n\n"; } else if (s == "filter") { - std::cout << " Filter visible reads.\n" + out << " Filter visible reads.\n" " Reads can be filtered using an expression '{property} {operation} {value}' (the white-spaces are also needed).\n" " For example, here are some useful expressions:\n" " filter mapq >= 20\n" @@ -129,44 +123,47 @@ namespace Term { " filter mapq > 0 [0, :] # Row 0, all columns (the first bam only, all regions)\n" " filter mapq > 0 [1, -1] # Row 1, last column\n\n"; } else if (s == "find" || s == "f") { - std::cout << " Find a read with name.\n All alignments with the same name will be highlighted with a black border\n Examples:\n 'find D00360:18:H8VC6ADXX:1:1107:5538:24033'\n\n"; + out << " Find a read with name.\n All alignments with the same name will be highlighted with a black border\n Examples:\n 'find D00360:18:H8VC6ADXX:1:1107:5538:24033'\n\n"; } else if (s == "goto") { - std::cout << " Navigate to a locus or track feature.\n This moves the current region to a new view point. You can specify a genome locus, or a feature name from one of the loaded tracks\n Examples:\n 'goto chr1' \n 'goto hTERT' # this will search all tracks for an entry called 'hTERT' \n\n"; + out << " Navigate to a locus or track feature.\n This moves the current region to a new view point. You can specify a genome locus, or a feature name from one of the loaded tracks\n Examples:\n 'goto chr1' \n 'goto hTERT' # this will search all tracks for an entry called 'hTERT' \n\n"; } else if (s == "grid") { - std::cout << " Set the grid size.\n Set the number of images displayed in a grid when using --variant option\n Examples:\n 'grid 8x8' # this will display 64 image tiles\n\n"; + out << " Set the grid size.\n Set the number of images displayed in a grid when using --variant option\n Examples:\n 'grid 8x8' # this will display 64 image tiles\n\n"; } else if (s == "indel-length") { - std::cout << " Set the minimum indel-length.\n Indels (gaps in alignments) will be labelled with text if they have length >= 'indel-length'\n Examples:\n 'indel-length 30'\n\n"; + out << " Set the minimum indel-length.\n Indels (gaps in alignments) will be labelled with text if they have length >= 'indel-length'\n Examples:\n 'indel-length 30'\n\n"; } else if (s == "insertions" || s == "ins") { - std::cout << " Toggle insertions.\n Insertions smaller than 'indel-length' are turned on or off.\n\n"; + out << " Toggle insertions.\n Insertions smaller than 'indel-length' are turned on or off.\n\n"; } else if (s == "line") { - std::cout << " Toggle line.\n A vertical line will turn on/off.\n\n"; + out << " Toggle line.\n A vertical line will turn on/off.\n\n"; } else if (s == "link" || s == "l") { - std::cout << " Link alignments.\n This will change how alignments are linked, options are 'none', 'sv', 'all'.\n Examples:\n 'link sv', 'link all'\n\n"; + 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 == "log2-cov") { - std::cout << " Toggle log2-coverage.\n The coverage track will be scaled by log2.\n\n"; + out << " Toggle log2-coverage.\n The coverage track will be scaled by log2.\n\n"; } else if (s == "mate") { - std::cout << " Goto mate alignment.\n Either moves the current view to the mate locus, or adds a new view of the mate locus.\n Examples:\n 'mate', 'mate add'\n\n"; + out << " Goto mate alignment.\n Either moves the current view to the mate locus, or adds a new view of the mate locus.\n Examples:\n 'mate', 'mate add'\n\n"; } else if (s == "mismatches" || s == "mm") { - std::cout << " Toggle mismatches.\n Mismatches with the reference genome are turned on or off.\n\n"; + out << " Toggle mismatches.\n Mismatches with the reference genome are turned on or off.\n\n"; } else if (s == "online") { - std::cout << " Show links to online browsers for the current region.\n A genome tag may need to be added e.g. 'online hg38'\n\n"; + out << " Show links to online browsers for the current region.\n A genome tag may need to be added e.g. 'online hg38'\n\n"; } else if (s == "refresh" || s == "r") { - std::cout << " Refresh the drawing.\n All filters will be removed any everything will be redrawn.\n\n"; + out << " Refresh the drawing.\n All filters will be removed any everything will be redrawn.\n\n"; } else if (s == "remove" || s == "rm") { - std::cout << " Remove a region, bam or track.\n Remove a region, bam or track by index. To remove a bam or track add a 'bam' or 'track' prefix.\n Examples:\n 'rm 0', 'rm bam1', 'rm track2'\n\n"; + out << " Remove a region, bam or track.\n Remove a region, bam or track by index. To remove a bam or track add a 'bam' or 'track' prefix.\n Examples:\n 'rm 0', 'rm bam1', 'rm track2'\n\n"; } else if (s == "sam") { - std::cout << " Print the sam format of the read.\n First select a read using the mouse then type ':sam'.\n\n"; + 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") { - std::cout << " Save reads, snapshot or session to file.\n" - " The filepath extension you use will determine the output file type.\n" + out << " Save reads, snapshot or session to file.\n" + " The filepath extension you use will determine the output file type.\n\n" " Examples:\n" " 'save reads.bam' # Save all visible reads to reads.bam file. Any filters are applied.\n" " 'save reads.cram' # Reads saved in cram format, the loaded reference genome to configure\n" " 'save reads.sam' # Reads saved in sam format (human readable)\n" " 'save view.png' # The current view is saved to view.png. Same functionality as 'snapshot' command\n" - " 'save session.xml' # The current session will be saved, allowing this session to be revisited\n\n"; + " 'save session.xml' # The current session will be saved, allowing this session to be revisited\n\n" + " Notes:\n" + " If multiple bams are open, reads are saved in sorted order. Some issues may arise if headers\n" + " are different between different input files\n\n"; } else if (s == "snapshot" || s == "s") { - std::cout << " Save an image of the screen.\n" + out << " Save an image of the screen.\n" " Saves current window. If no name is provided, the image name will be 'chrom_start_end.png', \n" " or if you are in tile-mode, the image name will be 'index_start_end.png'.\n" " snapshot\n" @@ -180,15 +177,15 @@ namespace Term { " you can press the repeat key (R) to repeat the last command, which can save typing this\n" " command over and over.\n\n"; } else if (s == "soft-clips" || s == "sc") { - std::cout << " Toggle soft-clips.\n Soft-clipped bases or hard-clips are turned on or off.\n\n"; + out << " Toggle soft-clips.\n Soft-clipped bases or hard-clips are turned on or off.\n\n"; } else if (s == "tags") { - std::cout << " Print selected sam tags.\n This will print all the tags of the selected read\n\n"; + out << " Print selected sam tags.\n This will print all the tags of the selected read\n\n"; } else if (s == "theme") { - std::cout << " Switch the theme.\n Currently 'igv', 'dark' or 'slate' themes are supported.\n\n"; + out << " Switch the theme.\n Currently 'igv', 'dark' or 'slate' themes are supported.\n\n"; } else if (s == "tlen-y") { - std::cout << " Toggle --tlen-y option.\n The --tlen-y option scales reads by template length. Applies to paired-end reads only.\n\n"; + out << " Toggle --tlen-y option.\n The --tlen-y option scales reads by template length. Applies to paired-end reads only.\n\n"; } else if (s == "var" || s == "v") { - std::cout << " Print variant information.\n" + out << " Print variant information.\n" " Using 'var' will print the selected variant.\n" " If you are viewing a vcf/bcf then columns can be parsed e.g:\n" " var pos # position\n" @@ -200,17 +197,17 @@ namespace Term { " you can press ENTER to repeat the last command, which can save typing this\n" " command over and over.\n\n"; } else if (s == "ylim") { - std::cout << " Set the y limit.\n The y limit is the maximum depth shown on the drawing e.g. 'ylim 100'.\n\n"; + out << " Set the y limit.\n The y limit is the maximum depth shown on the drawing e.g. 'ylim 100'.\n\n"; } else { - std::cerr << termcolor::red << "Error:" << termcolor::reset << " no manual for command " << s << std::endl; + out << termcolor::red << "Error:" << termcolor::reset << " no manual for command " << s << std::endl; } } constexpr char basemap[] = {'.', 'A', 'C', '.', 'G', '.', '.', '.', 'T', '.', '.', '.', '.', '.', 'N', 'N', 'N'}; - void clearLine() { + void clearLine(std::ostream& out) { std::string s(Utils::get_terminal_width(), ' '); - std::cout << "\r" << s << std::flush; + out << "\r" << s << std::flush; } void editInputText(std::string &inputText, const char *letter, int &charIndex) { @@ -223,7 +220,7 @@ namespace Term { } } - void printCigar(std::vector::iterator r) { + void printCigar(std::vector::iterator r, std::ostream& out) { uint32_t l, cigar_l, op, k; uint32_t *cigar_p; cigar_l = r->delegate->core.n_cigar; @@ -235,35 +232,35 @@ namespace Term { if (cigar_l > 30 && !(k == 0 || k == cigar_l - 1)) { if (print_dots) { - std::cout << " ... "; + out << " ... "; print_dots = false; } continue; } if (op == 0) { - std::cout << l << "M"; + out << l << "M"; } else if (op == 1) { - std::cout << termcolor::magenta << l << "I" << termcolor::reset; + out << termcolor::magenta << l << "I" << termcolor::reset; } else if (op == 2) { - std::cout << termcolor::red << l << "D"<< termcolor::reset; + out << termcolor::red << l << "D"<< termcolor::reset; } else if (op == 8) { - std::cout << l << "X"; + out << l << "X"; } else if (op == 4) { - std::cout << termcolor::bright_blue << l << "S"<< termcolor::reset; + out << termcolor::bright_blue << l << "S"<< termcolor::reset; } else if (op == 5) { - std::cout << termcolor::blue << l << "H" << termcolor::reset; + out << termcolor::blue << l << "H" << termcolor::reset; } else { - std::cout << termcolor::blue << l << "?" << termcolor::reset; + out << termcolor::blue << l << "?" << termcolor::reset; } } } - void printSeq(std::vector::iterator r, const char *refSeq, int refStart, int refEnd, int max=500) { + void printSeq(std::vector::iterator r, const char *refSeq, int refStart, int refEnd, int max, std::ostream& out) { auto l_seq = (int)r->delegate->core.l_qseq; if (l_seq == 0) { - std::cout << "*"; + out << "*"; return; } uint32_t l, cigar_l, op, k; @@ -277,14 +274,14 @@ namespace Term { op = cigar_p[k] & BAM_CIGAR_MASK; l = cigar_p[k] >> BAM_CIGAR_SHIFT; if (i >= max || (int)l >= max) { - std::cout << "..."; + out << "..."; return; } if (op == BAM_CHARD_CLIP) { continue; } else if (op == BAM_CDEL) { for (int n=0; n < (int)l; ++n) { - std::cout << "-"; + out << "-"; } p += l; } else if (op == BAM_CMATCH) { @@ -295,40 +292,40 @@ namespace Term { mm = true; } if (mm) { - std::cout << termcolor::underline; + out << termcolor::underline; switch (basemap[base]) { case 65 : - std::cout << termcolor::green << "A" << termcolor::reset; + out << termcolor::green << "A" << termcolor::reset; break; case 67 : - std::cout << termcolor::blue << "C" << termcolor::reset; + out << termcolor::blue << "C" << termcolor::reset; break; case 71 : - std::cout << termcolor::yellow << "G" << termcolor::reset; + out << termcolor::yellow << "G" << termcolor::reset; break; case 78 : - std::cout << termcolor::grey << "N" << termcolor::reset; + out << termcolor::grey << "N" << termcolor::reset; break; case 84 : - std::cout << termcolor::red << "T" << termcolor::reset; + out << termcolor::red << "T" << termcolor::reset; break; } } else { switch (basemap[base]) { case 65 : - std::cout << "A"; + out << "A"; break; case 67 : - std::cout << "C"; + out << "C"; break; case 71 : - std::cout << "G"; + out << "G"; break; case 78 : - std::cout << "N"; + out << "N"; break; case 84 : - std::cout << "T"; + out << "T"; break; } } @@ -341,19 +338,19 @@ namespace Term { uint8_t base = bam_seqi(ptr_seq, i); switch (basemap[base]) { case 65 : - std::cout << "A"; + out << "A"; break; case 67 : - std::cout << "C"; + out << "C"; break; case 71 : - std::cout << "G"; + out << "G"; break; case 78 : - std::cout << "N"; + out << "N"; break; case 84 : - std::cout << "T"; + out << "T"; break; } i += 1; @@ -365,19 +362,19 @@ namespace Term { uint8_t base = bam_seqi(ptr_seq, i); switch (basemap[base]) { case 65 : - std::cout << termcolor::green << "A" << termcolor::reset; + out << termcolor::green << "A" << termcolor::reset; break; case 67 : - std::cout << termcolor::blue << "C" << termcolor::reset; + out << termcolor::blue << "C" << termcolor::reset; break; case 71 : - std::cout << termcolor::yellow << "G" << termcolor::reset; + out << termcolor::yellow << "G" << termcolor::reset; break; case 78 : - std::cout << termcolor::grey << "N" << termcolor::reset; + out << termcolor::grey << "N" << termcolor::reset; break; case 84 : - std::cout << termcolor::red << "T" << termcolor::reset; + out << termcolor::red << "T" << termcolor::reset; break; } i += 1; @@ -388,11 +385,11 @@ namespace Term { for (int n=0; n < (int)l; ++n) { // soft-clips uint8_t base = bam_seqi(ptr_seq, i); switch (basemap[base]) { - case 65 : std::cout << termcolor::green << "A" << termcolor::reset; break; - case 67 : std::cout << termcolor::blue << "C" << termcolor::reset; break; - case 71 : std::cout << termcolor::yellow << "G" << termcolor::reset; break; - case 78 : std::cout << termcolor::grey << "N" << termcolor::reset; break; - case 84 : std::cout << termcolor::red << "T" << termcolor::reset; break; + case 65 : out << termcolor::green << "A" << termcolor::reset; break; + case 67 : out << termcolor::blue << "C" << termcolor::reset; break; + case 71 : out << termcolor::yellow << "G" << termcolor::reset; break; + case 78 : out << termcolor::grey << "N" << termcolor::reset; break; + case 84 : out << termcolor::red << "T" << termcolor::reset; break; } i += 1; } @@ -400,143 +397,73 @@ namespace Term { } } - void read2sam(std::vector::iterator r, const sam_hdr_t* hdr, std::string &sam, bool low_mem) { - uint32_t l, cigar_l, op, k; - uint32_t *cigar_p; - cigar_l = r->delegate->core.n_cigar; - cigar_p = bam_get_cigar(r->delegate); - const char *rname = sam_hdr_tid2name(hdr, r->delegate->core.tid); - std::string d = "\t"; - std::ostringstream oss; - oss << bam_get_qname(r->delegate) << d - << r->delegate->core.flag << d - << rname << d - << r->pos + 1 << d - << (int)r->delegate->core.qual << d; - if (cigar_l) { - for (k = 0; k < cigar_l; k++) { - op = cigar_p[k] & BAM_CIGAR_MASK; - l = cigar_p[k] >> BAM_CIGAR_SHIFT; - oss << l; - switch (op) { - case 0: oss << "M"; break; - case 1: oss << "I"; break; - case 2: oss << "D"; break; - case 3: oss << "N"; break; - case 4: oss << "S"; break; - case 5: oss << "H"; break; - case 6: oss << "P"; break; - case 7: oss << "="; break; - case 8: oss << "X"; break; - default: oss << "B"; - } - } oss << d; - } else { - oss << "*" << d; - } - if (r->delegate->core.mtid < 0) { - oss << "*" << d; - } else if (r->delegate->core.mtid == r->delegate->core.tid) { - oss << "=" << d; - } else { - oss << sam_hdr_tid2name(hdr, r->delegate->core.mtid) << d; - } - oss << r->delegate->core.mpos + 1 << d; - oss << r->delegate->core.isize << d; - if (r->delegate->core.l_qseq) { - uint8_t *ptr_seq = bam_get_seq(r->delegate); - for (int n = 0; n < r->delegate->core.l_qseq; ++n) { - oss << basemap[bam_seqi(ptr_seq, n)]; - } - - if (!low_mem) { - oss << d; - uint8_t *ptr_qual = bam_get_qual(r->delegate); - for (int n = 0; n < r->delegate->core.l_qseq; ++n) { - uint8_t qual = ptr_qual[n]; - oss << (char)(qual + 33); - } - oss << d; - } - } else { - oss << "*" << d << "*" << d; - } - - uint8_t *s = bam_get_aux(r->delegate); - uint8_t *end = r->delegate->data + r->delegate->l_data; - kstring_t str = { 0, 0, nullptr }; - while (end - s >= 4) { - kputc_('\t', &str); - if ((s = (uint8_t *)sam_format_aux1(s, s[2], s+3, end, &str)) == nullptr) { - - } - } - kputsn("", 0, &str); // nul terminate - char * si = str.s; - for (int n = 0; n < (int)str.l; ++n) { - oss << *si; - si ++; + void read2sam(std::vector::iterator r, const sam_hdr_t* hdr, std::string &sam, bool low_mem, std::ostream& out) { + kstring_t kstr = {0, 0, nullptr}; + int res = sam_format1(hdr, r->delegate, &kstr); + if (res < 0) { + out << termcolor::red << "Error:" << termcolor::reset << " failed to parse alignment record\n"; } - sam = oss.str(); - ks_free(&str); + sam = kstr.s; + ks_free(&kstr); + return; } - void printRead(std::vector::iterator r, const sam_hdr_t* hdr, std::string &sam, const char *refSeq, int refStart, int refEnd, bool low_mem) { + void printRead(std::vector::iterator r, const sam_hdr_t* hdr, std::string &sam, const char *refSeq, int refStart, int refEnd, bool low_mem, std::ostream& out) { const char *rname = sam_hdr_tid2name(hdr, r->delegate->core.tid); const char *rnext = sam_hdr_tid2name(hdr, r->delegate->core.mtid); - std::cout << std::endl << std::endl; - std::cout << termcolor::bold << "qname " << termcolor::reset << bam_get_qname(r->delegate) << std::endl; - std::cout << termcolor::bold << "span " << termcolor::reset << rname << ":" << r->pos << "-" << r->reference_end << std::endl; + out << std::endl << std::endl; + out << termcolor::bold << "qname " << termcolor::reset << bam_get_qname(r->delegate) << std::endl; + out << termcolor::bold << "span " << termcolor::reset << rname << ":" << r->pos << "-" << r->reference_end << std::endl; if (rnext) { - std::cout << termcolor::bold << "mate " << termcolor::reset << rnext << ":" << r->delegate->core.mpos << std::endl; + out << termcolor::bold << "mate " << termcolor::reset << rnext << ":" << r->delegate->core.mpos << std::endl; } - std::cout << termcolor::bold << "flag " << termcolor::reset << r->delegate->core.flag << std::endl; - std::cout << termcolor::bold << "mapq " << termcolor::reset << (int)r->delegate->core.qual << std::endl; - std::cout << termcolor::bold << "cigar " << termcolor::reset; printCigar(r); std::cout << std::endl; - std::cout << termcolor::bold << "seq " << termcolor::reset; printSeq(r, refSeq, refStart, refEnd); std::cout << std::endl << std::endl; + out << termcolor::bold << "flag " << termcolor::reset << r->delegate->core.flag << std::endl; + out << termcolor::bold << "mapq " << termcolor::reset << (int)r->delegate->core.qual << std::endl; + out << termcolor::bold << "cigar " << termcolor::reset; printCigar(r, out); out << std::endl; + out << termcolor::bold << "seq " << termcolor::reset; printSeq(r, refSeq, refStart, refEnd, 500, out); out << std::endl << std::endl; - read2sam(r, hdr, sam, low_mem); + read2sam(r, hdr, sam, low_mem, out); } - void printSelectedSam(std::string &sam) { - std::cout << std::endl << sam << std::endl << std::endl; + void printSelectedSam(std::string &sam, std::ostream& out) { + out << std::endl << sam << std::endl << std::endl; } - void printKeyFromValue(int v) { + void printKeyFromValue(int v, std::ostream& out) { ankerl::unordered_dense::map key_table; Keys::getKeyTable(key_table); for (auto &p: key_table) { if (p.second == v) { - std::cout << p.first; + out << p.first; break; } } } - void printRefSeq(Utils::Region *region, float x, float xOffset, float xScaling) { + void printRefSeq(Utils::Region *region, float x, float xOffset, float xScaling, std::ostream& out) { float min_x = xOffset; float max_x = xScaling * ((float)(region->end - region->start)) + min_x; int size = region->end - region->start; if (x > min_x && x < max_x && size <= 20000) { const char * s = region->refSeq; - std::cout << "\n\n" << region->chrom << ":" << region->start << "-" << region->end << "\n"; + out << "\n\n" << region->chrom << ":" << region->start << "-" << region->end << "\n"; while (*s) { switch ((unsigned int)*s) { - case 65: std::cout << termcolor::green << "a"; break; - case 67: std::cout << termcolor::blue << "c"; break; - case 71: std::cout << termcolor::yellow << "g"; break; - case 78: std::cout << termcolor::bright_grey << "n"; break; - case 84: std::cout << termcolor::red << "t"; break; - case 97: std::cout << termcolor::green << "A"; break; - case 99: std::cout << termcolor::blue << "C"; break; - case 103: std::cout << termcolor::yellow << "G"; break; - case 110: std::cout << termcolor::bright_grey << "N"; break; - case 116: std::cout << termcolor::red << "T"; break; - default: std::cout << "?"; break; + case 65: out << termcolor::green << "a"; break; + case 67: out << termcolor::blue << "c"; break; + case 71: out << termcolor::yellow << "g"; break; + case 78: out << termcolor::bright_grey << "n"; break; + case 84: out << termcolor::red << "t"; break; + case 97: out << termcolor::green << "A"; break; + case 99: out << termcolor::blue << "C"; break; + case 103: out << termcolor::yellow << "G"; break; + case 110: out << termcolor::bright_grey << "N"; break; + case 116: out << termcolor::red << "T"; break; + default: out << "?"; break; } ++s; } - std::cout << termcolor::reset << std::endl << std::endl; + out << termcolor::reset << std::endl << std::endl; return; } } @@ -552,7 +479,7 @@ namespace Term { return s; } - void printCoverage(int pos, Segs::ReadCollection &cl) { + void printCoverage(int pos, Segs::ReadCollection &cl, std::ostream& out) { if (cl.readQueue.empty()) { return; } @@ -693,52 +620,52 @@ namespace Term { return; } - std::cout << termcolor::bold << "\rCoverage " << termcolor::reset << totCov << " "; + out << termcolor::bold << "\rCoverage " << termcolor::reset << totCov << " "; if (A) { - std::cout << " A:" << A; + out << " A:" << A; } else if (T) { - std::cout << " T:" << T; + out << " T:" << T; } else if (C) { - std::cout << " C:" << C; + out << " C:" << C; } else if (G) { - std::cout << " G:" << G; + out << " G:" << G; } else { - std::cout << " "; + out << " "; } term_space -= (int)line.size(); line.clear(); line = " A:" + std::to_string(mA) + " T:" + std::to_string(mT) + " C:" + std::to_string(mC) + " G:" + std::to_string(mT); if (term_space < (int)line.size()) { - std::cout << std::flush; + out << std::flush; return; } if (mA > 0 || mT > 0 || mC > 0 || mG > 0) { if (mA) { - std::cout << termcolor::green << " A" << termcolor::reset << ":" << mA; + out << termcolor::green << " A" << termcolor::reset << ":" << mA; } if (mT) { - std::cout << termcolor::red << " T" << termcolor::reset << ":"<< mT; + out << termcolor::red << " T" << termcolor::reset << ":"<< mT; } if (mC) { - std::cout << termcolor::blue << " C" << termcolor::reset << ":" << mC; + out << termcolor::blue << " C" << termcolor::reset << ":" << mC; } if (mG) { - std::cout << termcolor::yellow << " G" << termcolor::reset << ":" << mG; + out << termcolor::yellow << " G" << termcolor::reset << ":" << mG; } } line.clear(); std::string s = intToStringCommas(pos); line = " Pos " + s; if (term_space < (int)line.size()) { - std::cout << std::flush; + out << std::flush; return; } - std::cout << termcolor::bold << " Pos " << termcolor::reset << s; - std::cout << std::flush; + out << termcolor::bold << " Pos " << termcolor::reset << s; + out << std::flush; } - void printTrack(float x, HGW::GwTrack &track, Utils::Region *rgn, bool mouseOver, int targetLevel, int trackIdx, std::string &target_name, int *target_pos) { + void printTrack(float x, HGW::GwTrack &track, Utils::Region *rgn, bool mouseOver, int targetLevel, int trackIdx, std::string &target_name, int *target_pos, std::ostream& out) { if (rgn == nullptr) { return; } @@ -751,7 +678,7 @@ namespace Term { bool same_pos, same_name = false; for (auto &b : rgn->featuresInView.at(trackIdx)) { if (b.start <= target && b.end >= target && b.level == targetLevel) { - clearLine(); + clearLine(out); if (target_name == b.name) { same_name = true; } else { @@ -763,27 +690,27 @@ namespace Term { *target_pos = b.start; } - std::cout << "\r" << termcolor::bold << p.filename().string() << termcolor::reset << " " << \ + out << "\r" << termcolor::bold << p.filename().string() << termcolor::reset << " " << \ termcolor::cyan << b.chrom << ":" << b.start << "-" << b.end << termcolor::reset; if (!b.parent.empty()) { - std::cout << termcolor::bold << " Parent " << termcolor::reset << b.parent; + out << termcolor::bold << " Parent " << termcolor::reset << b.parent; } else if (!b.name.empty()) { - std::cout << termcolor::bold << " ID " << termcolor::reset << b.name; + out << termcolor::bold << " ID " << termcolor::reset << b.name; target_name = b.name; } if (!b.vartype.empty()) { - std::cout << termcolor::bold << " Type " << termcolor::reset << b.vartype; + out << termcolor::bold << " Type " << termcolor::reset << b.vartype; } - std::cout << std::flush; + out << std::flush; if (same_name && same_pos && !mouseOver && !b.line.empty()) { - std::cout << "\n" << b.line << std::endl; + out << "\n" << b.line << std::endl; return; } if (!mouseOver) { - std::cout << std::endl; + out << std::endl; std::vector &parts = b.parts; if (isGFF) { int i = 0; int j = 0; @@ -796,8 +723,8 @@ namespace Term { bool first = true; assert (j >=9); for (int k=j-9; k <= j; ++k) { - if (first) { std::cout << parts[k]; first = false;} - else { std::cout << "\t" << parts[k]; } + if (first) { out << parts[k]; first = false;} + else { out << "\t" << parts[k]; } } } break; @@ -809,11 +736,11 @@ namespace Term { } else { bool first = true; for (auto &p: parts) { - if (first) { std::cout << p; first = false; } - else { std::cout << "\t" << p; } + if (first) { out << p; first = false; } + else { out << "\t" << p; } } } - std::cout << std::endl; + out << std::endl; } else { break; } @@ -821,76 +748,76 @@ namespace Term { } if (!mouseOver) { - std::cout << std::endl; + out << std::endl; } } - void printVariantFileInfo(Utils::Label *label, int index) { + void printVariantFileInfo(Utils::Label *label, int index, std::ostream& out) { if (label->pos < 0) { return; } - Term::clearLine(); + Term::clearLine(out); std::string v = "\rPos "; int term_space = Utils::get_terminal_width(); if (term_space < (int)v.size()) { - std::cout << std::flush; return; + out << std::flush; return; } - std::cout << termcolor::bold << v << termcolor::reset; + out << termcolor::bold << v << termcolor::reset; term_space -= (int)v.size(); if (label->pos != -1) { v = label->chrom + ":" + std::to_string(label->pos); if (term_space < (int)v.size()) { - std::cout << std::flush; return; + out << std::flush; return; } - std::cout << v; + out << v; term_space -= (int)v.size(); } v = " ID "; if (term_space < (int)v.size()) { - std::cout << std::flush; return; + out << std::flush; return; } - std::cout << termcolor::bold << v << termcolor::reset; + out << termcolor::bold << v << termcolor::reset; term_space -= (int)v.size(); v = label->variantId; if (term_space < (int)v.size()) { - std::cout << std::flush; return; + out << std::flush; return; } - std::cout << v; + out << v; term_space -= (int)v.size(); v = " Type "; if (term_space < (int)v.size()) { - std::cout << std::flush; return; + out << std::flush; return; } - std::cout << termcolor::bold << v << termcolor::reset; + out << termcolor::bold << v << termcolor::reset; term_space -= (int)v.size(); if (!label->vartype.empty()) { v = label->vartype; if (term_space < (int)v.size()) { - std::cout << std::flush; return; + out << std::flush; return; } - std::cout << v; + out << v; term_space -= (int)v.size(); } v = " Index "; if (term_space < (int)v.size()) { - std::cout << std::flush; return; + out << std::flush; return; } - std::cout << termcolor::bold << v << termcolor::reset; + out << termcolor::bold << v << termcolor::reset; term_space -= (int)v.size(); v = std::to_string(index); if (term_space < (int)v.size()) { - std::cout << std::flush; return; + out << std::flush; return; } - std::cout << v; + out << v; term_space -= (int)v.size(); - std::cout << std::flush; + out << std::flush; } int check_url(const char *url) { @@ -914,23 +841,23 @@ namespace Term { link = std::regex_replace(link, end_pattern, std::to_string(end)); } - int checkAndPrintRegionLink(std::string &link, const char *info) { + int checkAndPrintRegionLink(std::string &link, const char *info, std::ostream& out) { if (check_url(link.c_str())) { - std::cout << termcolor::green << "\n" << info << "\n" << termcolor::reset; - std::cout << link << std::endl; + out << termcolor::green << "\n" << info << "\n" << termcolor::reset; + out << link << std::endl; return 1; } return 0; } - void printOnlineLinks(std::vector &tracks, Utils::Region &rgn, std::string &genome_tag) { + void printOnlineLinks(std::vector &tracks, Utils::Region &rgn, std::string &genome_tag, std::ostream& out) { if (genome_tag.empty()) { - std::cerr << termcolor::red << "Error:" << termcolor::reset + out << termcolor::red << "Error:" << termcolor::reset << " a 'genome_tag' must be provided for the current reference genome e.g. hg19 or hs1" << std::endl; return; } - std::cout << "\nFetching online resources for genome: " << termcolor::bold << genome_tag << termcolor::reset + out << "\nFetching online resources for genome: " << termcolor::bold << genome_tag << termcolor::reset << std::endl; std::string chrom = rgn.chrom; @@ -954,71 +881,71 @@ namespace Term { if (is_hg19) { link = "https://genome.ucsc.edu/cgi-bin/hgTracks?db=hg19&lastVirtModeType=default&lastVirtModeExtraState=&virtModeType=default&virtMode=0&nonVirtPosition=&position=CHROM%3ASTART%2DEND&hgsid=1791027784_UAbj7DxAIuZZsoF0z5BQmYQgoS6j"; replaceRegionInLink(chrom_pattern, start_pattern, end_pattern, link, chrom, rgn.start, rgn.end); - p += checkAndPrintRegionLink(link, "UCSC Genome Browser GRCh37"); + p += checkAndPrintRegionLink(link, "UCSC Genome Browser GRCh37", out); link = "https://www.deciphergenomics.org/browser#q/grch37:CHROM:START-END/location/CHROM:START-END"; replaceRegionInLink(chrom_pattern, start_pattern, end_pattern, link, chrom_no_chr, rgn.start, rgn.end); - p += checkAndPrintRegionLink(link, "DECIPHER Genome Browser GRCh37"); + p += checkAndPrintRegionLink(link, "DECIPHER Genome Browser GRCh37", out); link = "https://gnomad.broadinstitute.org/region/CHROM-START-END?dataset=gnomad_r2_1"; replaceRegionInLink(chrom_pattern, start_pattern, end_pattern, link, chrom_no_chr, rgn.start, rgn.end); - p += checkAndPrintRegionLink(link, "Gnomad Genome Browser v2.1"); + p += checkAndPrintRegionLink(link, "Gnomad Genome Browser v2.1", out); link = "http://grch37.ensembl.org/Homo_sapiens/Location/View?r=CHROM:START-END"; replaceRegionInLink(chrom_pattern, start_pattern, end_pattern, link, chrom_no_chr, rgn.start, rgn.end); - p += checkAndPrintRegionLink(link, "Ensemble Genome Browser GRCh37"); + p += checkAndPrintRegionLink(link, "Ensemble Genome Browser GRCh37", out); link = "https://www.ncbi.nlm.nih.gov/genome/gdv/browser/genome/?chr=CHROM&from=START&to=END&id=GCF_000001405.40"; replaceRegionInLink(chrom_pattern, start_pattern, end_pattern, link, chrom_no_chr, rgn.start, rgn.end); - p += checkAndPrintRegionLink(link, "NCBI Genome Browser GRCh37"); + p += checkAndPrintRegionLink(link, "NCBI Genome Browser GRCh37", out); } else if (is_hg38) { link = "https://genome.ucsc.edu/cgi-bin/hgTracks?db=hg38&lastVirtModeType=default&lastVirtModeExtraState=&virtModeType=default&virtMode=0&nonVirtPosition=&position=CHROM%3ASTART%2DEND&hgsid=1791027784_UAbj7DxAIuZZsoF0z5BQmYQgoS6j"; replaceRegionInLink(chrom_pattern, start_pattern, end_pattern, link, chrom, rgn.start, rgn.end); - p += checkAndPrintRegionLink(link, "UCSC Genome Browser GRCh38"); + p += checkAndPrintRegionLink(link, "UCSC Genome Browser GRCh38", out); link = "https://www.deciphergenomics.org/browser#q/CHROM:START-END/location/CHROM:START-END"; replaceRegionInLink(chrom_pattern, start_pattern, end_pattern, link, chrom_no_chr, rgn.start, rgn.end); - p += checkAndPrintRegionLink(link, "DECIPHER Genome Browser GRCh38"); + p += checkAndPrintRegionLink(link, "DECIPHER Genome Browser GRCh38", out); link = "https://gnomad.broadinstitute.org/region/CHROM-START-END?dataset=gnomad_sv_r4"; replaceRegionInLink(chrom_pattern, start_pattern, end_pattern, link, chrom_no_chr, rgn.start, rgn.end); - p += checkAndPrintRegionLink(link, "Gnomad Genome Browser v4"); + p += checkAndPrintRegionLink(link, "Gnomad Genome Browser v4", out); link = "http://www.ensembl.org/Homo_sapiens/Location/View?r=CHROM%3ASTART-END"; replaceRegionInLink(chrom_pattern, start_pattern, end_pattern, link, chrom_no_chr, rgn.start, rgn.end); - p += checkAndPrintRegionLink(link, "Ensemble Genome Browser GRCh38"); + p += checkAndPrintRegionLink(link, "Ensemble Genome Browser GRCh38", out); link = "https://www.ncbi.nlm.nih.gov/genome/gdv/browser/genome/?chr=CHROM&from=START&to=END&id=GCF_000001405.40"; replaceRegionInLink(chrom_pattern, start_pattern, end_pattern, link, chrom_no_chr, rgn.start, rgn.end); - p += checkAndPrintRegionLink(link, "NCBI Genome Browser GRCh38"); + p += checkAndPrintRegionLink(link, "NCBI Genome Browser GRCh38", out); } else if (is_t2t) { link = "https://genome.ucsc.edu/cgi-bin/hgTracks?db=hub_3267197_GCA_009914755.4&lastVirtModeType=default&lastVirtModeExtraState=&virtModeType=default&virtMode=0&nonVirtPosition=&position=CHROM%3ASTART%2DEND&hgsid=1791153048_ztJI6SNAA8eGqv01Z11jfkDfa76v"; replaceRegionInLink(chrom_pattern, start_pattern, end_pattern, link, chrom, rgn.start, rgn.end); - p += checkAndPrintRegionLink(link, "UCSC Genome Browser T2T v2.0"); + p += checkAndPrintRegionLink(link, "UCSC Genome Browser T2T v2.0", out); link = "https://rapid.ensembl.org/Homo_sapiens_GCA_009914755.4/Location/View?r=CHROM%3ASTART-END"; replaceRegionInLink(chrom_pattern, start_pattern, end_pattern, link, chrom_no_chr, rgn.start, rgn.end); - p += checkAndPrintRegionLink(link, "Ensemble Genome Browser T2T v2.0"); + p += checkAndPrintRegionLink(link, "Ensemble Genome Browser T2T v2.0", out); link = "https://www.ncbi.nlm.nih.gov/genome/gdv/browser/genome/?chr=CHROM&from=START&to=END&id=GCF_009914755.1"; replaceRegionInLink(chrom_pattern, start_pattern, end_pattern, link, chrom, rgn.start, rgn.end); - p += checkAndPrintRegionLink(link, "NCBI Genome Browser T2T v2.0"); + p += checkAndPrintRegionLink(link, "NCBI Genome Browser T2T v2.0", out); } else { link = "https://genome-euro.ucsc.edu/cgi-bin/hgTracks?db=GENOME&lastVirtModeType=default&lastVirtModeExtraState=&virtModeType=default&virtMode=0&nonVirtPosition=&position=CHROM%3ASTART%2DEND&hgsid=308573407_B0yK8LZQyOQXcYLjPy7a3u2IW7an"; link = std::regex_replace(link, genome_pattern, genome_tag); replaceRegionInLink(chrom_pattern, start_pattern, end_pattern, link, chrom, rgn.start, rgn.end); - p += checkAndPrintRegionLink(link, genome_tag.c_str()); + p += checkAndPrintRegionLink(link, genome_tag.c_str(), out); } if (!p) { - std::cout << "Could not find find any valid links, invalid genome_tag? Should be e.g. hg19 or mm39 etc\n"; + out << "Could not find find any valid links, invalid genome_tag? Should be e.g. hg19 or mm39 etc\n"; } else { - std::cout << std::endl; + out << std::endl; } } - void updateRefGenomeSeq(Utils::Region *region, float xW, float xOffset, float xScaling) { + void updateRefGenomeSeq(Utils::Region *region, float xW, float xOffset, float xScaling, std::ostream& out) { float min_x = xOffset; float max_x = xScaling * ((float)(region->end - region->start)) + min_x; if (xW > min_x && xW < max_x) { @@ -1040,27 +967,27 @@ namespace Term { return; } const char * s = ®ion->refSeq[startIdx]; - Term::clearLine(); - std::cout << termcolor::bold << "\rRef " << termcolor::reset ; + Term::clearLine(out); + out << termcolor::bold << "\rRef " << termcolor::reset ; int l = 0; while (*s && l < chars) { switch ((unsigned int)*s) { // this is a bit of mess, but gets the job done - case 65: ((l == i) ? (std::cout << termcolor::underline << termcolor::green << "a" << termcolor::reset) : (std::cout << termcolor::green << "a") ); break; - case 67: ((l == i) ? (std::cout << termcolor::underline << termcolor::blue << "c" << termcolor::reset) : (std::cout << termcolor::blue << "c") ); break; - case 71: ((l == i) ? (std::cout << termcolor::underline << termcolor::yellow << "g" << termcolor::reset) : (std::cout << termcolor::yellow << "g") ); break; - case 78: ((l == i) ? (std::cout << termcolor::underline << termcolor::bright_grey << "n" << termcolor::reset) : (std::cout << termcolor::bright_grey << "n") ); break; - case 84: ((l == i) ? (std::cout << termcolor::underline << termcolor::red << "t" << termcolor::reset) : (std::cout << termcolor::red << "t") ); break; - case 97: ((l == i) ? (std::cout << termcolor::underline << termcolor::green << "A" << termcolor::reset) : (std::cout << termcolor::green << "A") ); break; - case 99: ((l == i) ? (std::cout << termcolor::underline << termcolor::blue << "C" << termcolor::reset) : (std::cout << termcolor::blue << "C") ); break; - case 103: ((l == i) ? (std::cout << termcolor::underline << termcolor::yellow << "G" << termcolor::reset) : (std::cout << termcolor::yellow << "G") ); break; - case 110: ((l == i) ? (std::cout << termcolor::underline << termcolor::bright_grey << "N" << termcolor::reset) : (std::cout << termcolor::bright_grey << "N") ); break; - case 116: ((l == i) ? (std::cout << termcolor::underline << termcolor::red << "T" << termcolor::reset) : (std::cout << termcolor::red << "T") ); break; - default: std::cout << "?"; break; + case 65: ((l == i) ? (out << termcolor::underline << termcolor::green << "a" << termcolor::reset) : (out << termcolor::green << "a") ); break; + case 67: ((l == i) ? (out << termcolor::underline << termcolor::blue << "c" << termcolor::reset) : (out << termcolor::blue << "c") ); break; + case 71: ((l == i) ? (out << termcolor::underline << termcolor::yellow << "g" << termcolor::reset) : (out << termcolor::yellow << "g") ); break; + case 78: ((l == i) ? (out << termcolor::underline << termcolor::bright_grey << "n" << termcolor::reset) : (out << termcolor::bright_grey << "n") ); break; + case 84: ((l == i) ? (out << termcolor::underline << termcolor::red << "t" << termcolor::reset) : (out << termcolor::red << "t") ); break; + case 97: ((l == i) ? (out << termcolor::underline << termcolor::green << "A" << termcolor::reset) : (out << termcolor::green << "A") ); break; + case 99: ((l == i) ? (out << termcolor::underline << termcolor::blue << "C" << termcolor::reset) : (out << termcolor::blue << "C") ); break; + case 103: ((l == i) ? (out << termcolor::underline << termcolor::yellow << "G" << termcolor::reset) : (out << termcolor::yellow << "G") ); break; + case 110: ((l == i) ? (out << termcolor::underline << termcolor::bright_grey << "N" << termcolor::reset) : (out << termcolor::bright_grey << "N") ); break; + case 116: ((l == i) ? (out << termcolor::underline << termcolor::red << "T" << termcolor::reset) : (out << termcolor::red << "T") ); break; + default: out << "?"; break; } ++s; l += 1; } - std::cout << termcolor::reset << std::flush; + out << termcolor::reset << std::flush; return; } } diff --git a/src/term_out.h b/src/term_out.h index 71165e2..909a17a 100644 --- a/src/term_out.h +++ b/src/term_out.h @@ -20,31 +20,34 @@ namespace Term { - void help(Themes::IniOptions &opts); + void help(Themes::IniOptions &opts, std::ostream& out); - void manuals(std::string &s); + void manuals(std::string &s, std::ostream& out); - void clearLine(); + void clearLine(std::ostream& out); void editInputText(std::string &inputText, const char *letter, int &charIndex); - void printRead(std::vector::iterator r, const sam_hdr_t* hdr, std::string &sam, const char *refSeq, int refStart, int refEnd, bool low_mem); + void printRead(std::vector::iterator r, const sam_hdr_t* hdr, std::string &sam, const char *refSeq, + int refStart, int refEnd, bool low_mem, std::ostream& out); - void printSelectedSam(std::string &sam); + void printSelectedSam(std::string &sam, std::ostream& out); - void printKeyFromValue(int v); + void printKeyFromValue(int v, std::ostream& out); std::string intToStringCommas(int pos); - void printRefSeq(Utils::Region *region, float x, float xOffset, float xScaling); + void printRefSeq(Utils::Region *region, float x, float xOffset, float xScaling, std::ostream& out); - void printCoverage(int pos, Segs::ReadCollection &cl); + void printCoverage(int pos, Segs::ReadCollection &cl, std::ostream& out); - void printTrack(float x, HGW::GwTrack &track, Utils::Region *rgn, bool mouseOver, int targetLevel, int trackIdx, std::string &target_name, int *target_pos); + void printTrack(float x, HGW::GwTrack &track, Utils::Region *rgn, bool mouseOver, int targetLevel, int trackIdx, + std::string &target_name, int *target_pos, std::ostream& out); - void printVariantFileInfo(Utils::Label *label, int index); + void printVariantFileInfo(Utils::Label *label, int index, std::ostream& out); - void printOnlineLinks(std::vector &tracks, Utils::Region &rgn, std::string &genome_tag); + void printOnlineLinks(std::vector &tracks, Utils::Region &rgn, std::string &genome_tag, + std::ostream& out); - void updateRefGenomeSeq(Utils::Region *region, float xW, float xOffset, float xScaling); + void updateRefGenomeSeq(Utils::Region *region, float xW, float xOffset, float xScaling, std::ostream& out); } From 712943cbfc456a6fbda0111c9b27a6bf93fd234f Mon Sep 17 00:00:00 2001 From: kcleal Date: Tue, 14 May 2024 10:58:48 +0100 Subject: [PATCH 30/51] Command interface done. Option to redirect output to ostringstream. Fixed custom .png bug. Started work on save command --- src/plot_commands.cpp | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/src/plot_commands.cpp b/src/plot_commands.cpp index e39e277..5d128bd 100644 --- a/src/plot_commands.cpp +++ b/src/plot_commands.cpp @@ -841,6 +841,24 @@ namespace Commands { return Err::NONE; } + // Err write_bam() + + Err save_command(Plot* p, std::vector parts, std::ostream& out) { + if (parts.size() == 1) { + return Err::OPTION_NOT_UNDERSTOOD; + } + if (parts.size() == 2) { + if (Utils::endsWith(parts.back(), ".bam") || Utils::endsWith(parts.back(), ".cram")) { + + } + } else if (parts.size() == 3 && (parts[1] == ">" || parts[1] == ">>")) { + + } else { + return Err::OPTION_NOT_UNDERSTOOD; + } + return Err::NONE; + } + Err infer_region_or_feature(Plot* p, std::string& command, std::vector parts) { Utils::Region rgn; Err reason = Err::NONE; @@ -893,23 +911,6 @@ namespace Commands { return reason; } -// Err write_bam() - - Err infer_region_or_feature(Plot* p, std::string& command, std::vector parts) { - if (parts.size() == 1) { - return Err::OPTION_NOT_UNDERSTOOD; - } - if (parts.size() == 2) { - if (Utils::endsWith(parts.back(), ".bam") || Utils::endsWith(parts.back(), ".cram")) { - - } - } else if (parts.size() == 3 && (parts[1] == ">" || parts[1] == ">>") { - - } else { - return Err::OPTION_NOT_UNDERSTOOD; - } - } - void handle_err(Err result, std::ostream& out) { switch (result) { case NONE: break; @@ -1009,7 +1010,7 @@ namespace Commands { {"s", PARAMS { return snapshot(p, parts, out); }}, {"snapshot", PARAMS { return snapshot(p, parts, out); }}, {"online", PARAMS { return online(p, parts, out); }}, - {"save", PARAMS { return online(p, parts, out); }}, + {"save", PARAMS { return save_command(p, parts, out); }}, }; From 2211ced135a2c7ab02d5eaae7e63e108b2f339ea Mon Sep 17 00:00:00 2001 From: kcleal Date: Tue, 14 May 2024 16:41:45 +0100 Subject: [PATCH 31/51] Better error reporting, more work on save command --- src/glfw_keys.cpp | 5 +- src/glfw_keys.h | 4 +- src/menu.cpp | 2 +- src/parser.cpp | 92 ++++++++++---------- src/parser.h | 17 ++-- src/plot_commands.cpp | 193 +++++++++++++++++++++++++++++++++++++++--- src/plot_controls.cpp | 15 ++-- src/plot_manager.cpp | 4 +- src/term_out.cpp | 2 +- src/themes.cpp | 34 +++++--- src/themes.h | 3 +- 11 files changed, 275 insertions(+), 96 deletions(-) diff --git a/src/glfw_keys.cpp b/src/glfw_keys.cpp index ca2cada..f8aa5ad 100644 --- a/src/glfw_keys.cpp +++ b/src/glfw_keys.cpp @@ -5,11 +5,11 @@ #include #include #include -#include "ankerl_unordered_dense.h" +#include namespace Keys { - void getKeyTable(ankerl::unordered_dense::map& kt) { + void getKeyTable(std::unordered_map& kt) { kt["SPACE"] = GLFW_KEY_SPACE; kt["APOSTROPHE"] = GLFW_KEY_APOSTROPHE; kt[","] = GLFW_KEY_COMMA; @@ -130,6 +130,7 @@ namespace Keys { kt["RIGHT_ALT"] = GLFW_KEY_RIGHT_ALT; kt["RIGHT_SUPER"] = GLFW_KEY_RIGHT_SUPER; kt["MENU"] = GLFW_KEY_MENU; + kt["#"] = GLFW_KEY_WORLD_1; // Could also be GLFW_KEY_WORLD_2 } } diff --git a/src/glfw_keys.h b/src/glfw_keys.h index 43c27a3..e723994 100644 --- a/src/glfw_keys.h +++ b/src/glfw_keys.h @@ -6,10 +6,10 @@ #include #include #include -#include "ankerl_unordered_dense.h" +#include namespace Keys { - void getKeyTable(ankerl::unordered_dense::map& kt); + void getKeyTable(std::unordered_map& kt); } diff --git a/src/menu.cpp b/src/menu.cpp index 0824e37..d5837d0 100644 --- a/src/menu.cpp +++ b/src/menu.cpp @@ -760,7 +760,7 @@ namespace Menu { } void applyKeyboardKeyOption(Option &new_opt, Themes::IniOptions &opts) { - ankerl::unordered_dense::map keys; + std::unordered_map keys; Keys::getKeyTable(keys); std::string k = new_opt.value; for(auto &c : k) { c = toupper(c); } diff --git a/src/parser.cpp b/src/parser.cpp index 208d2cd..037cb4c 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -19,7 +19,7 @@ namespace Parse { constexpr std::string_view numeric_like = "eq ne gt lt ge le == != > < >= <="; constexpr std::string_view string_like = "eq ne contains == != omit"; - Parser::Parser() { + Parser::Parser(std::ostream& errOutput) : out(errOutput) { opMap["mapq"] = MAPQ; opMap["flag"] = FLAG; opMap["~flag"] = NFLAG; @@ -136,7 +136,7 @@ namespace Parse { } - int parse_indexing(std::string &s, int nBams, int nRegions, std::vector< std::vector > &v) { + int parse_indexing(std::string &s, size_t nBams, size_t nRegions, std::vector< std::vector > &v, std::ostream& out) { // check for indexing. Makes a lookup table which describes which panels a filter should be applied to std::string::iterator iStart = s.end(); std::string::iterator iEnd = s.end(); @@ -161,7 +161,7 @@ namespace Parse { return 0; } if (!open != !close) { // xor - std::cerr << "Error: expression not understood: " << s << std::endl; + out << "Error: expression not understood: " << s << std::endl; return -1; } auto indexStr = std::string(iStart, iEnd); @@ -171,7 +171,7 @@ namespace Parse { return 1; } if (nBams == 0 || nRegions == 0) { - std::cerr << "Error: No bam/region to filter. Trying to apply a filter to nBams==" << nBams << " and nRegions==" << nRegions << std::endl; + out << "Error: No bam/region to filter. Trying to apply a filter to nBams==" << nBams << " and nRegions==" << nRegions << std::endl; return -1; } std::string lhs, rhs; @@ -189,7 +189,7 @@ namespace Parse { } rhs = std::string(iStart, itr); if (nBams > 1 && lhs.empty()) { - std::cerr << "Error: if multiple bams are present you need to specify the [row,column] e.g. [:, 0] or [0,1] etc\n"; + out << "Error: if multiple bams are present you need to specify the [row,column] e.g. [:, 0] or [0,1] etc\n"; return -1; } bool allRows = lhs == ":"; @@ -199,28 +199,27 @@ namespace Parse { iRow = (lhs.empty()) ? 0 : (allRows) ? -1 : std::stoi(lhs); iCol = (allColumns) ? 0 : std::stoi(rhs); } catch (...) { - std::cerr << "Error: string to integer failed for left-hand side=" << lhs << ", or right-hand side=" << rhs << std::endl; + out << "Error: string to integer failed for left-hand side=" << lhs << ", or right-hand side=" << rhs << std::endl; return -1; } - - if (std::abs(iRow) >= nBams) { - std::cerr << "Error: row index is > nBams\n"; + iRow = (iRow < 0) ? nBams + iRow : iRow; // support negative indexing + iCol = (iCol < 0) ? nRegions + iCol : iCol; + if (std::abs(iRow) >= (int)nBams) { + out << "Error: row index is > nBams\n"; return -1; } - if (std::abs(iCol) >= nRegions) { - std::cerr << "Error: column index is > nRegions\n"; + if (std::abs(iCol) >= (int)nRegions) { + out << "Error: column index is > nRegions\n"; return -1; } - iRow = (iRow < 0) ? nBams + iRow : iRow; // support negative indexing - iCol = (iCol < 0) ? nRegions + iCol : iCol; - v.resize(nBams, std::vector(nRegions)); - for (int r=0; r < nBams; ++r) { - for (int c=0; c < nRegions; ++c) { - if (allRows && c==iCol) { + v.resize(nBams, std::vector(nRegions)); + for (size_t r=0; r < nBams; ++r) { + for (size_t c=0; c < nRegions; ++c) { + if (allRows && c==(size_t)iCol) { v[r][c] = 1; - } else if (allColumns && r==iRow) { + } else if (allColumns && r==(size_t)iRow) { v[r][c] = 1; - } else if (c==iCol && r==iRow) { + } else if (c==(size_t)iCol && r==(size_t)iRow) { v[r][c] = 1; } } @@ -238,7 +237,7 @@ namespace Parse { orBlock = true; } - int res = parse_indexing(s, nBams, nRegions, targetIndexes); + int res = parse_indexing(s, nBams, nRegions, targetIndexes, out); if (res < 0) { return res; } @@ -262,7 +261,7 @@ namespace Parse { output = {"~flag", "&", output[0]}; } } else { - std::cerr << "Expression not understood, need three components as {property} {operator} {value}, or a named value for flag. Found: " << token << std::endl; + out << "Expression not understood, need three components as {property} {operator} {value}, or a named value for flag. Found: " << token << std::endl; return -1; } } @@ -283,7 +282,7 @@ namespace Parse { output = {"~flag", "&", output[0]}; } } else { - std::cerr << "Expression not understood, need three components as {property} {operator} {value}, or a named value for flag. Found: " << token << std::endl; + out << "Expression not understood, need three components as {property} {operator} {value}, or a named value for flag. Found: " << token << std::endl; return -1; } } @@ -304,7 +303,7 @@ namespace Parse { output = {"~flag", "&", output[0]}; } } else { - std::cerr << "Expression not understood, need three components as {property} {operator} {value}, or a named value for flag. Found: " << token << std::endl; + out << "Expression not understood, need three components as {property} {operator} {value}, or a named value for flag. Found: " << token << std::endl; return -1; } } @@ -322,11 +321,11 @@ namespace Parse { int Parser::prep_evaluations(std::vector &evaluations, std::vector &output) { if (! opMap.contains(output[0])) { - std::cerr << "Left-hand side property not available: " << output[0] << std::endl; + out << "Left-hand side property not available: " << output[0] << std::endl; return -1; } if (! opMap.contains(output[1])) { - std::cerr << "Middle operation not available: " << output[1] << std::endl; + out << "Middle operation not available: " << output[1] << std::endl; return -1; } Property lhs = opMap[output[0]]; @@ -334,7 +333,7 @@ namespace Parse { std::string allowed = permit[lhs]; if (allowed.find(output[1]) == std::string::npos) { - std::cerr << output[0] << " is only compatible with: " << allowed << std::endl; + out << output[0] << " is only compatible with: " << allowed << std::endl; return -1; } @@ -380,8 +379,8 @@ namespace Parse { } else if (output.back() == "supplementary") { e.ival = Property::SUPPLEMENTARY; } else { - std::cerr << "Right-hand side value must be an integer or named-value: " << output[2] << std::endl; - std::cerr << "Named values can be one of: paired, proper-pair, unmapped, munmap, reverse, mreverse, read1, read2, secondary, qcfail, dup, supplementary\n"; + out << "Right-hand side value must be an integer or named-value: " << output[2] << std::endl; + out << "Named values can be one of: paired, proper-pair, unmapped, munmap, reverse, mreverse, read1, read2, secondary, qcfail, dup, supplementary\n"; return -1; } } @@ -390,7 +389,7 @@ namespace Parse { e.op = mid; e.sval = output.back(); } else { - std::cerr << "Left-hand side operation not available: " << output[0] << std::endl; return -1; + out << "Left-hand side operation not available: " << output[0] << std::endl; return -1; } evaluations.push_back(e); return 1; @@ -399,7 +398,7 @@ namespace Parse { int Parser::set_filter(std::string &s, int nBams, int nRegions) { filter_str = s; if ( (s.find("or") != std::string::npos) && (s.find("and") != std::string::npos) ) { - std::cerr << "Filter block must be either composed of 'or' expressions, or 'and' expressions, not both\n"; + out << "Filter block must be either composed of 'or' expressions, or 'and' expressions, not both\n"; return -1; } int res1 = split_into_or(s, evaluations_block, nBams, nRegions); @@ -663,7 +662,7 @@ namespace Parse { std::vector filters; for (auto &s: Utils::split(str, ';')) { - Parse::Parser p = Parse::Parser(); + Parse::Parser p = Parse::Parser(out); int rr = p.set_filter(s, nBams, nRegions); if (rr > 0) { filters.push_back(p); @@ -757,7 +756,7 @@ namespace Parse { } } - void get_value_in_brackets(std::string &requestBracket) { + void get_value_in_brackets(std::string &requestBracket, std::ostream& out) { const std::regex singleBracket("\\["); if (std::regex_search(requestBracket, singleBracket)) { const std::regex bracketRegex("\\[(.*?)\\]"); @@ -765,7 +764,7 @@ namespace Parse { if (std::regex_search(requestBracket, bracketMatch, bracketRegex)) { requestBracket = bracketMatch[1].str(); } else { - std::cerr << "Cannot parse sample inside [] from input string. \n"; + out << "Cannot parse sample inside [] from input string. \n"; } } else { requestBracket = "0"; @@ -778,8 +777,8 @@ namespace Parse { return !s.empty() && it == s.end(); } - void convert_name_index(std::string &requestBracket, int &i, std::vector &sample_names) { - get_value_in_brackets(requestBracket); + void convert_name_index(std::string &requestBracket, int &i, std::vector &sample_names, std::ostream& out) { + get_value_in_brackets(requestBracket, out); Utils::trim(requestBracket); if (is_number(requestBracket)) { i = std::stoi(requestBracket); @@ -787,12 +786,12 @@ namespace Parse { } else { ptrdiff_t index = std::distance(sample_names.begin(), std::find(sample_names.begin(), sample_names.end(), requestBracket)); if ((int)index >= (int)sample_names.size()) { - std::cerr << "Sample not in file: " << requestBracket << std::endl; - std::cerr << "Samples listed in file are: "; + out << "Sample not in file: " << requestBracket << std::endl; + out << "Samples listed in file are: "; for (auto &samp : sample_names) { - std::cerr << samp << " "; + out << samp << " "; } - std::cerr << std::endl; + out << std::endl; return; } i = (int)index + 9; @@ -819,14 +818,14 @@ namespace Parse { } } - void parse_FORMAT (std::string &result, std::vector &vcfCols, std::string &request, std::vector &sample_names) { + void parse_FORMAT (std::string &result, std::vector &vcfCols, std::string &request, std::vector &sample_names, std::ostream& out) { if (request == "format" || request == "genome" || request == "format[0]") { result = vcfCols[9]; return; } std::string requestBracket = request; int i = 0; - convert_name_index(requestBracket, i, sample_names); + convert_name_index(requestBracket, i, sample_names, out); if (i == 0 || i >= (int)vcfCols.size()) { throw std::invalid_argument("request was invalid"); } @@ -850,7 +849,7 @@ namespace Parse { } - void parse_vcf_split(std::string &result, std::vector &vcfCols, std::string &request, std::vector &sample_names) { + void parse_vcf_split(std::string &result, std::vector &vcfCols, std::string &request, std::vector &sample_names, std::ostream& out) { if (request == "chrom") { result = vcfCols[0]; } else if (request == "pos") { @@ -868,10 +867,9 @@ namespace Parse { } else if (request == "info" || Utils::startsWith(request, "info.")) { parse_INFO(result, vcfCols[7], request); } else if (request == "format" || Utils::startsWith(request, "format.") || Utils::startsWith(request, "format[")) { - parse_FORMAT(result, vcfCols, request, sample_names); + parse_FORMAT(result, vcfCols, request, sample_names, out); } else { - std::cerr << "Valid fields are chrom, pos, id, ref, alt, qual, filter, info, format\n"; - throw std::invalid_argument("request was invalid"); + out << termcolor::red << "Error:" << termcolor::reset << " could not parse. Valid fields are chrom, pos, id, ref, alt, qual, filter, info, format\n"; } } @@ -902,7 +900,7 @@ namespace Parse { } } - void parse_output_name_format(std::string &nameFormat, std::vector &vcfCols, std::vector &sample_names, std::vector &bam_paths, std::string &label) { + void parse_output_name_format(std::string &nameFormat, std::vector &vcfCols, std::vector &sample_names, std::vector &bam_paths, std::string &label, std::ostream& out) { std::regex bash("\\{(.*?)\\}"); std::smatch matches; std::string test = nameFormat; @@ -916,7 +914,7 @@ namespace Parse { if (matches.size() == 2) { std::string tmpVal = matches[1]; if (tmpVal != "sample" && tmpVal != "label") { - parse_vcf_split(value, vcfCols, tmpVal, sample_names); + parse_vcf_split(value, vcfCols, tmpVal, sample_names, out); } else if (tmpVal == "sample") { parse_sample_variable(value, bam_paths); } else if (tmpVal == "label") { diff --git a/src/parser.h b/src/parser.h index 75ab0d6..39336ed 100644 --- a/src/parser.h +++ b/src/parser.h @@ -84,15 +84,16 @@ namespace Parse { class Parser { public: - Parser(); - ~Parser() {}; + Parser(std::ostream& errOutput); + ~Parser() = default; bool orBlock; std::string filter_str; ankerl::unordered_dense::map< std::string, Property> opMap; ankerl::unordered_dense::map< Property, std::string> permit; std::vector evaluations_block; - std::vector< std::vector > targetIndexes; + std::vector< std::vector > targetIndexes; + std::ostream& out; int set_filter(std::string &f, int nBams, int nRegions); bool eval(const Segs::Align &aln, const sam_hdr_t* hdr, int bamIdx, int regionIdx); @@ -105,17 +106,19 @@ namespace Parse { void countExpression(std::vector &collections, std::string &str, std::vector hdrs, std::vector &bam_paths, int nBams, int nRegions, std::ostream& out); - void parse_INFO(std::string &line, std::string &infoCol, std::string &request); + void parse_INFO(std::string &line, std::string &infoCol, std::string &request, std::ostream& out); - void parse_FORMAT(std::string &line, std::vector &vcfCols, std::string &request, std::vector &sample_names); + void parse_FORMAT(std::string &line, std::vector &vcfCols, std::string &request, std::vector &sample_names, std::ostream& out); - void parse_vcf_split(std::string &line, std::vector &vcfCols, std::string &request, std::vector &sample_names); + void parse_vcf_split(std::string &line, std::vector &vcfCols, std::string &request, std::vector &sample_names, std::ostream& out); void parse_output_name_format(std::string &nameFormat, std::vector &vcfCols, std::vector &sample_names, - std::vector &bam_paths, std::string &label); + std::vector &bam_paths, std::string &label, std::ostream& out); void parse_sample_variable(std::string &fname, std::vector &bam_paths); + int parse_indexing(std::string &s, size_t nBams, size_t nRegions, std::vector< std::vector > &v, std::ostream& out); + } diff --git a/src/plot_commands.cpp b/src/plot_commands.cpp index 5d128bd..89e3415 100644 --- a/src/plot_commands.cpp +++ b/src/plot_commands.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -127,7 +128,8 @@ namespace Commands { std::filesystem::path homedir(home); std::filesystem::path inputPath(fpath); path = homedir / inputPath; - return path; + std::string stringpath = path.generic_string(); + return stringpath; } Err sam(Plot* p, std::string& command, std::vector& parts, std::ostream& out) { @@ -375,9 +377,9 @@ namespace Commands { for (size_t i = 1; i < requests; ++i) { std::string result; try { - Parse::parse_vcf_split(result, vcfCols, parts[i], sample_names_copy); + Parse::parse_vcf_split(result, vcfCols, parts[i], sample_names_copy, out); } catch (...) { - out << termcolor::red << "Error:" << termcolor::reset << " could not parse " << parts[i] << std::endl; + out << termcolor::red << "Error:" << termcolor::reset << " could not parse " << parts[i] << ". Valid fields are chrom, pos, id, ref, alt, qual, filter, info, format\n"; return Err::PARSE_VCF; } if (i != requests-1) { @@ -421,7 +423,7 @@ namespace Commands { return Err::NONE; } for (auto &s: Utils::split(str, ';')) { - Parse::Parser ps = Parse::Parser(); + Parse::Parser ps = Parse::Parser(out); int rr = ps.set_filter(s, (int)p->bams.size(), (int)p->regions.size()); if (rr > 0) { p->filters.push_back(ps); @@ -803,7 +805,7 @@ namespace Commands { } std::vector vcfCols = Utils::split(variantStringCopy, '\t'); try { - Parse::parse_output_name_format(nameFormat, vcfCols, sample_names_copy, p->bam_paths, lbl.current()); + Parse::parse_output_name_format(nameFormat, vcfCols, sample_names_copy, p->bam_paths, lbl.current(), out); } catch (...) { return Err::PARSE_INPUT; } @@ -841,21 +843,183 @@ namespace Commands { return Err::NONE; } - // Err write_bam() + Err write_bam(Plot* p, std::string& o_str, std::vector< std::vector> targets, std::ostream& out, bool append=false) { - Err save_command(Plot* p, std::vector parts, std::ostream& out) { - if (parts.size() == 1) { - return Err::OPTION_NOT_UNDERSTOOD; + sam_hdr_t* hdr = p->headers[p->regionSelection]; + cram_fd* fc = nullptr; + htsFile *h_out = nullptr; + bool write_cram = false; + int res; + std::string full_path = tilde_to_home(o_str); + const char* outf = full_path.c_str(); + if (!append) { + out << "Creating new file: " << outf << "\n"; + if (Utils::endsWith(o_str, ".sam")) { + h_out = hts_open(outf, "w"); + res = sam_hdr_write(h_out, hdr); + if (res < 0) { + out << termcolor::red << "Error:" << termcolor::reset << " Failed to copy header\n"; + sam_close(h_out); + return Err::NONE; + } + } else if (Utils::endsWith(o_str, ".bam")) { + h_out = hts_open(outf, "wb"); + res = sam_hdr_write(h_out, hdr); + if (res < 0) { + out << termcolor::red << "Error:" << termcolor::reset << " Failed to copy header\n"; + sam_close(h_out); + return Err::NONE; + } + } else { + h_out = hts_open(outf, "wc"); + fc = h_out->fp.cram; + write_cram = true; + cram_fd_set_header(fc, hdr); + res = sam_hdr_write(h_out, hdr); + if (res < 0) { + out << termcolor::red << "Error:" << termcolor::reset << " Failed to copy header\n"; + sam_close(h_out); + return Err::NONE; + } + } + } else { + out << "Appending to file: " << outf << "\n"; + if (Utils::endsWith(o_str, ".sam")) { + h_out = hts_open(outf, "a"); + } else if (Utils::endsWith(o_str, ".bam")) { + h_out = hts_open(outf, "ab"); + } else { + h_out = hts_open(outf, "ac"); + fc = h_out->fp.cram; + write_cram = true; + } } - if (parts.size() == 2) { - if (Utils::endsWith(parts.back(), ".bam") || Utils::endsWith(parts.back(), ".cram")) { + std::vector region_iters; + std::vector file_ptrs; + if (targets.empty()) { + for (size_t i=0; i < p->regions.size(); ++i) { + int start = p->regions[i].start; + int end = p->regions[i].end; + for (size_t j=0; j < p->bams.size(); ++j) { + sam_hdr_t* hdr_ptr = p->headers[j]; + int tid = sam_hdr_name2tid(hdr_ptr, p->regions[i].chrom.c_str()); + hts_idx_t* index = p->indexes[j]; + hts_itr_t* it = sam_itr_queryi(index, tid, start, end); + region_iters.push_back(it); + file_ptrs.push_back(p->bams[j]); + } } - } else if (parts.size() == 3 && (parts[1] == ">" || parts[1] == ">>")) { - } else { + for (const auto& t : targets) { + sam_hdr_t *hdr_ptr = p->headers[t[0]]; + int tid = sam_hdr_name2tid(hdr_ptr, p->regions[t[1]].chrom.c_str()); + int start = p->regions[t[1]].start; + int end = p->regions[t[1]].end; + hts_idx_t* index = p->indexes[t[0]]; + hts_itr_t* it = sam_itr_queryi(index, tid, start, end); + region_iters.push_back(it); + file_ptrs.push_back(p->bams[t[0]]); + } + } + auto compare = [](bam1_t* a, bam1_t* b) -> bool { + if (a->core.tid < b->core.tid) { + return true; + } else if (a->core.tid == b->core.tid) { + return a->core.pos <= b->core.pos; + } else { + return false; + } + }; + std::priority_queue, decltype(compare)> align_q(compare); + + while (region_iters.size() > 0) { + for (size_t i=0; i < region_iters.size(); ++i) { + bam1_t* a = bam_init1(); + if (sam_itr_next(file_ptrs[i], region_iters[i], a) >= 0) { + align_q.push(a); + } else { + region_iters.erase(region_iters.begin() + i); + file_ptrs.erase(file_ptrs.begin() + i); + bam_destroy1(a); + break; + } + } + + } + + for (auto & item : region_iters) { + bam_itr_destroy(item); + } + +// +// bam1_t* b = bam_init1(); +// kstring_t kstr = {0, 0, nullptr}; +// std::string& stdStr = p->selectedAlign; +// kstr.l = stdStr.size(); +// kstr.m = stdStr.size() + 1; +// kstr.s = (char*)malloc(kstr.m); +// memcpy(kstr.s, stdStr.data(), stdStr.size()); +// kstr.s[kstr.l] = '\0'; +// std::cerr << kstr.s << std::endl; +// res = sam_parse1(&kstr, hdr, b); +// free(kstr.s); +// if (res < 0) { +// out << termcolor::red << "Error:" << termcolor::reset << " Could not convert str to bam1_t\n"; +// bam_destroy1(b); +// sam_close(h_out); +// return Err::NONE; +// } +// if (!write_cram) { +// hts_set_fai_filename(h_out, p->reference.c_str()); +// hts_set_threads(h_out, p->opts.threads); +// } else { +// cram_set_option(fc, CRAM_OPT_REFERENCE, p->fai); +// cram_set_option(fc, CRAM_OPT_NTHREADS, p->opts.threads); +// } +// res = sam_write1(h_out, hdr, b); +// if (res < 0) { +// out << termcolor::red << "Error:" << termcolor::reset << "Write failed\n"; +// } +// bam_destroy1(b); +// sam_close(h_out); + + + // targets is rows and columns + return Err::NONE; + } + + Err save_command(Plot* p, std::string& command, std::vector parts, std::ostream& out) { + if (parts.size() == 1) { return Err::OPTION_NOT_UNDERSTOOD; } + auto as_alignments = [](std::string &s) -> bool { return (Utils::endsWith(s, ".sam") || Utils::endsWith(s, ".bam") || Utils::endsWith(s, ".cram")); }; + std::vector< std::vector> targets; + int res = Parse::parse_indexing(command, p->bams.size(), p->regions.size(), targets, out); + if (res < 0) { + return Err::SILENT; + } + if (res) { + Utils::trim(command); + parts = Utils::split(command, ' '); + } + if (as_alignments(parts.back())) { + if (parts.size() == 2) { + write_bam(p, parts.back(), targets, out); + } else if (parts.size() == 3) { + if (parts[1] == ">") { + write_bam(p, parts.back(), targets, out); + } else if (parts[1] == ">>") { + write_bam(p, parts.back(), targets, out, true); + } else { + return Err::OPTION_NOT_UNDERSTOOD; + } + } else { + return Err::OPTION_NOT_UNDERSTOOD; + } + } + + return Err::NONE; } @@ -996,6 +1160,7 @@ namespace Commands { {"filter", PARAMS { return addFilter(p, command, out); }}, {"tags", PARAMS { return tags(p, command, out); }}, {"mate", PARAMS { return mate(p, command, out); }}, + {"save", PARAMS { return save_command(p, command, parts, out); }}, {"f", PARAMS { return findRead(p, parts, out); }}, {"find", PARAMS { return findRead(p, parts, out); }}, {"ylim", PARAMS { return setYlim(p, parts, out); }}, @@ -1010,7 +1175,7 @@ namespace Commands { {"s", PARAMS { return snapshot(p, parts, out); }}, {"snapshot", PARAMS { return snapshot(p, parts, out); }}, {"online", PARAMS { return online(p, parts, out); }}, - {"save", PARAMS { return save_command(p, parts, out); }}, + }; diff --git a/src/plot_controls.cpp b/src/plot_controls.cpp index ffcd31e..2dca647 100644 --- a/src/plot_controls.cpp +++ b/src/plot_controls.cpp @@ -349,16 +349,21 @@ namespace Manager { } } const char *letter = glfwGetKeyName(key, scancode); +// std::string letter_str(letter); +// if (letter_str == "\\") { +// std::cout << "yep\n"; +// } + if (letter || key == GLFW_KEY_SPACE) { if (key == GLFW_KEY_SPACE) { // deal with special keys first Term::editInputText(inputText, " ", charIndex); } else { - if (mods == GLFW_MOD_SHIFT && opts.shift_keymap.contains(key)) { - Term::editInputText(inputText, opts.shift_keymap[key].c_str(), charIndex); - } - else if (mods == GLFW_MOD_SHIFT) { // uppercase - std::string str = letter; + std::string str = letter; + if (mods == GLFW_MOD_SHIFT && opts.shift_keymap.find(str) != opts.shift_keymap.end()) { + Term::editInputText(inputText, opts.shift_keymap[str].c_str(), charIndex); + } else if (mods == GLFW_MOD_SHIFT) { // uppercase + std::transform(str.begin(), str.end(),str.begin(), ::toupper); Term::editInputText(inputText, str.c_str(), charIndex); } else { // normal text here diff --git a/src/plot_manager.cpp b/src/plot_manager.cpp index d792893..4c6d3e0 100644 --- a/src/plot_manager.cpp +++ b/src/plot_manager.cpp @@ -378,11 +378,9 @@ namespace Manager { } void GwPlot::addFilter(std::string &filter_str) { - Parse::Parser p = Parse::Parser(); + Parse::Parser p = Parse::Parser(outStr); if (p.set_filter(filter_str, bams.size(), regions.size()) > 0) { filters.push_back(p); - } else { - throw std::runtime_error("Error: --filter option not understood"); } } diff --git a/src/term_out.cpp b/src/term_out.cpp index 8fe3a07..5e66e77 100644 --- a/src/term_out.cpp +++ b/src/term_out.cpp @@ -430,7 +430,7 @@ namespace Term { } void printKeyFromValue(int v, std::ostream& out) { - ankerl::unordered_dense::map key_table; + std::unordered_map key_table; Keys::getKeyTable(key_table); for (auto &p: key_table) { if (p.second == v) { diff --git a/src/themes.cpp b/src/themes.cpp index 8c17f39..6314c9f 100644 --- a/src/themes.cpp +++ b/src/themes.cpp @@ -413,8 +413,7 @@ namespace Themes { void IniOptions::getOptionsFromIni() { - ankerl::unordered_dense::map key_table; - + std::unordered_map key_table; Keys::getKeyTable(key_table); theme_str = myIni["general"]["theme"]; @@ -502,17 +501,26 @@ namespace Themes { enter_interactive_mode = key_table[myIni["labelling"]["enter_interactive_mode"]]; if (myIni.has("shift_keymap")) { - shift_keymap[key_table[myIni["shift_keymap"]["ampersand"]]] = "&"; - shift_keymap[key_table[myIni["shift_keymap"]["bar"]]] = "|"; - shift_keymap[key_table[myIni["shift_keymap"]["colon"]]] = ":"; - shift_keymap[key_table[myIni["shift_keymap"]["curly_open"]]] = "{"; - shift_keymap[key_table[myIni["shift_keymap"]["curly_close"]]] = "}"; - shift_keymap[key_table[myIni["shift_keymap"]["dollar"]]] = "$"; - shift_keymap[key_table[myIni["shift_keymap"]["exclamation"]]] = "!"; - shift_keymap[key_table[myIni["shift_keymap"]["greater_than"]]] = ">"; - shift_keymap[key_table[myIni["shift_keymap"]["less_than"]]] = "<"; - shift_keymap[key_table[myIni["shift_keymap"]["tilde"]]] = "~"; - shift_keymap[key_table[myIni["shift_keymap"]["underscore"]]] = "_"; + shift_keymap["\\"] = "|"; + shift_keymap[";"] = ":"; + shift_keymap["["] = "{"; + shift_keymap["]"] = "}"; + shift_keymap["."] = ">"; + shift_keymap[","] = "<"; + shift_keymap["#"] = "~"; + shift_keymap["-"] = "_"; + shift_keymap["'"] = "@"; + shift_keymap["="] = "+"; + shift_keymap["1"] = "!"; + shift_keymap["2"] = "\""; + shift_keymap["3"] = "£"; + shift_keymap["4"] = "$"; + shift_keymap["5"] = "%"; + shift_keymap["6"] = "^"; + shift_keymap["7"] = "&"; + shift_keymap["8"] = "*"; + shift_keymap["9"] = "("; + shift_keymap["0"] = ")"; } } diff --git a/src/themes.h b/src/themes.h index a558603..0c490db 100644 --- a/src/themes.h +++ b/src/themes.h @@ -138,7 +138,8 @@ namespace Themes { ~IniOptions() {}; mINI::INIStructure myIni; - ankerl::unordered_dense::map shift_keymap; +// std::unordered_map shift_keymap; + std::unordered_map shift_keymap; BaseTheme theme; Utils::Dims dimensions, number; std::string genome_tag; From f1b0a5c6655bb36a12af1d845c78ea310c83d7cf Mon Sep 17 00:00:00 2001 From: kcleal Date: Wed, 15 May 2024 16:18:47 +0100 Subject: [PATCH 32/51] Better error reporting, Added save and load commands, started on tab completion --- src/parser.cpp | 70 +++++++++- src/parser.h | 3 + src/plot_commands.cpp | 306 ++++++++++++++++++++++-------------------- src/plot_controls.cpp | 12 +- src/term_out.cpp | 35 +++-- 5 files changed, 262 insertions(+), 164 deletions(-) diff --git a/src/parser.cpp b/src/parser.cpp index 037cb4c..6b669b6 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -7,8 +7,9 @@ #include #include #include -#include "ankerl_unordered_dense.h" +#include #include "termcolor.h" +#include "term_out.h" #include "utils.h" #include "parser.h" #include "segments.h" @@ -495,7 +496,7 @@ namespace Parse { bool block_result = true; - if (!targetIndexes.empty() && targetIndexes[bamIdx][regionIdx] == 0) { + if (bamIdx >= 0 && !targetIndexes.empty() && targetIndexes[bamIdx][regionIdx] == 0) { return true; } @@ -930,4 +931,69 @@ namespace Parse { } } } + + std::string tilde_to_home(std::string fpath) { + if (Utils::startsWith(fpath, "./")) { + fpath.erase(0, 2); + return fpath; + } + if (!Utils::startsWith(fpath, "~/")) { + return fpath; + } + fpath.erase(0, 2); +#if defined(_WIN32) || defined(_WIN64) + const char *homedrive_c = std::getenv("HOMEDRIVE"); + const char *homepath_c = std::getenv("HOMEPATH"); + std::string homedrive(homedrive_c ? homedrive_c : ""); + std::string homepath(homepath_c ? homepath_c : ""); + std::string home = homedrive + homepath; +#else + struct passwd *pw = getpwuid(getuid()); + std::string home(pw->pw_dir); +#endif + std::filesystem::path path; + std::filesystem::path homedir(home); + std::filesystem::path inputPath(fpath); + path = homedir / inputPath; + std::string stringpath = path.generic_string(); + return stringpath; + } + + void tryTabCompletion(std::string &inputText, std::ostream& out) { + std::vector parts = Utils::split(inputText, ' '); + std::string globstr; + parts.back() = tilde_to_home(parts.back()); + if (parts.back() == "." || parts.back() == "./") { + globstr = "*"; + } else { + globstr = parts.back() + "*"; + } + size_t width = (size_t)Utils::get_terminal_width(); + std::vector glob_paths = glob_cpp::glob(globstr); + if (glob_paths.size() == 1) { + inputText = parts[0] + " " + glob_paths[0].generic_string(); + return; + } + size_t i = 0; + for (auto &item : glob_paths) { + std::string s = item.filename().generic_string(); + if (s.size() < width) { + out << s; + width -= s.size(); + } else { + if (width > 6 && s.size() > 6) { + s.erase(s.begin() + width - 4, s.end()); + out << s << " ..."; + } + break; + } + if (i < glob_paths.size() - 2 && width > 2) { + out << " "; + width -= 2; + } else { + break; + } + } + out.flush(); + } } diff --git a/src/parser.h b/src/parser.h index 39336ed..3d59c71 100644 --- a/src/parser.h +++ b/src/parser.h @@ -119,6 +119,9 @@ namespace Parse { int parse_indexing(std::string &s, size_t nBams, size_t nRegions, std::vector< std::vector > &v, std::ostream& out); + std::string tilde_to_home(std::string fpath); + + void tryTabCompletion(std::string &inputText, std::ostream& out); } diff --git a/src/plot_commands.cpp b/src/plot_commands.cpp index 89e3415..02c12fa 100644 --- a/src/plot_commands.cpp +++ b/src/plot_commands.cpp @@ -46,6 +46,7 @@ namespace Commands { CHROM_NOT_IN_REFERENCE, FEATURE_NOT_IN_TRACKS, BAD_REGION, + OPTION_NOT_SUPPORTED, OPTION_NOT_UNDERSTOOD, INVALID_PATH, EMPTY_TRACKS, @@ -109,29 +110,6 @@ namespace Commands { return Err::NONE; } - std::string tilde_to_home(std::string fpath) { - if (fpath[0] != '~') { - return fpath; - } - fpath.erase(0, 2); -#if defined(_WIN32) || defined(_WIN64) - const char *homedrive_c = std::getenv("HOMEDRIVE"); - const char *homepath_c = std::getenv("HOMEPATH"); - std::string homedrive(homedrive_c ? homedrive_c : ""); - std::string homepath(homepath_c ? homepath_c : ""); - std::string home = homedrive + homepath; -#else - struct passwd *pw = getpwuid(getuid()); - std::string home(pw->pw_dir); -#endif - std::filesystem::path path; - std::filesystem::path homedir(home); - std::filesystem::path inputPath(fpath); - path = homedir / inputPath; - std::string stringpath = path.generic_string(); - return stringpath; - } - Err sam(Plot* p, std::string& command, std::vector& parts, std::ostream& out) { if (!p->selectedAlign.empty()) { if (command == "sam") { @@ -143,7 +121,7 @@ namespace Commands { htsFile *h_out = nullptr; bool write_cram = false; int res; - std::string full_path = tilde_to_home(parts[2]); + std::string full_path = Parse::tilde_to_home(parts[2]); const char* outf = full_path.c_str(); if (parts[1] == ">") { out << "Creating new file: " << outf << "\n"; @@ -843,149 +821,169 @@ namespace Commands { return Err::NONE; } - Err write_bam(Plot* p, std::string& o_str, std::vector< std::vector> targets, std::ostream& out, bool append=false) { - + Err write_bam(Plot* p, std::string& o_str, std::vector< std::vector> targets, std::ostream& out) { sam_hdr_t* hdr = p->headers[p->regionSelection]; cram_fd* fc = nullptr; htsFile *h_out = nullptr; - bool write_cram = false; - int res; - std::string full_path = tilde_to_home(o_str); + int res = 0; + std::string full_path = Parse::tilde_to_home(o_str); const char* outf = full_path.c_str(); - if (!append) { - out << "Creating new file: " << outf << "\n"; - if (Utils::endsWith(o_str, ".sam")) { - h_out = hts_open(outf, "w"); - res = sam_hdr_write(h_out, hdr); - if (res < 0) { - out << termcolor::red << "Error:" << termcolor::reset << " Failed to copy header\n"; - sam_close(h_out); - return Err::NONE; - } - } else if (Utils::endsWith(o_str, ".bam")) { - h_out = hts_open(outf, "wb"); - res = sam_hdr_write(h_out, hdr); - if (res < 0) { - out << termcolor::red << "Error:" << termcolor::reset << " Failed to copy header\n"; - sam_close(h_out); - return Err::NONE; - } - } else { - h_out = hts_open(outf, "wc"); - fc = h_out->fp.cram; - write_cram = true; - cram_fd_set_header(fc, hdr); - res = sam_hdr_write(h_out, hdr); - if (res < 0) { - out << termcolor::red << "Error:" << termcolor::reset << " Failed to copy header\n"; - sam_close(h_out); - return Err::NONE; - } + std::string idx_path; + int min_shift = 0; + out << "Creating new file: " << outf << "\n"; + if (Utils::endsWith(o_str, ".sam")) { + h_out = hts_open(outf, "w"); + res = sam_hdr_write(h_out, hdr); + if (res < 0) { + out << termcolor::red << "Error:" << termcolor::reset << " Failed to copy header\n"; + sam_close(h_out); + return Err::SILENT; + } + } else if (Utils::endsWith(o_str, ".bam")) { + h_out = hts_open(outf, "wb"); + res = hts_set_threads(h_out, p->opts.threads); + res = sam_hdr_write(h_out, hdr); + if (res < 0) { + out << termcolor::red << "Error:" << termcolor::reset << " Failed to copy header\n"; + sam_close(h_out); + return Err::SILENT; } + idx_path = o_str + ".bai"; } else { - out << "Appending to file: " << outf << "\n"; - if (Utils::endsWith(o_str, ".sam")) { - h_out = hts_open(outf, "a"); - } else if (Utils::endsWith(o_str, ".bam")) { - h_out = hts_open(outf, "ab"); - } else { - h_out = hts_open(outf, "ac"); - fc = h_out->fp.cram; - write_cram = true; + h_out = hts_open(outf, "wc"); + fc = h_out->fp.cram; + cram_set_option(fc, CRAM_OPT_REFERENCE, p->reference.c_str()); + cram_set_option(fc, CRAM_OPT_NTHREADS, p->opts.threads); + cram_fd_set_header(fc, hdr); + res = sam_hdr_write(h_out, hdr); + if (res < 0) { + out << termcolor::red << "Error:" << termcolor::reset << " Failed to copy header\n"; + sam_close(h_out); + return Err::SILENT; + } + idx_path = o_str + ".crai"; + min_shift = 14; + } + // start index + if (!idx_path.empty()) { + res = sam_idx_init(h_out, hdr, min_shift, idx_path.c_str()); + if (res < 0) { + out << termcolor::red << "Error:" << termcolor::reset << " Failed to make index\n"; + idx_path.clear(); } } + // use region array from htlib std::vector region_iters; std::vector file_ptrs; - if (targets.empty()) { + for (size_t j=0; j < p->bams.size(); ++j) { + sam_hdr_t* hdr_ptr = p->headers[j]; + hts_idx_t* index = p->indexes[j]; + std::vector region_names; for (size_t i=0; i < p->regions.size(); ++i) { - int start = p->regions[i].start; - int end = p->regions[i].end; - for (size_t j=0; j < p->bams.size(); ++j) { - sam_hdr_t* hdr_ptr = p->headers[j]; - int tid = sam_hdr_name2tid(hdr_ptr, p->regions[i].chrom.c_str()); - hts_idx_t* index = p->indexes[j]; - hts_itr_t* it = sam_itr_queryi(index, tid, start, end); - region_iters.push_back(it); - file_ptrs.push_back(p->bams[j]); + const auto &r = p->regions[i]; + if (!targets.empty()) { + if (targets[j][i]) { + region_names.push_back(r.chrom + ":" + std::to_string(r.start) + "-" + std::to_string(r.end)); + } + } else { + region_names.push_back(r.chrom + ":" + std::to_string(r.start) + "-" + std::to_string(r.end)); } } - } else { - for (const auto& t : targets) { - sam_hdr_t *hdr_ptr = p->headers[t[0]]; - int tid = sam_hdr_name2tid(hdr_ptr, p->regions[t[1]].chrom.c_str()); - int start = p->regions[t[1]].start; - int end = p->regions[t[1]].end; - hts_idx_t* index = p->indexes[t[0]]; - hts_itr_t* it = sam_itr_queryi(index, tid, start, end); - region_iters.push_back(it); - file_ptrs.push_back(p->bams[t[0]]); + if (region_names.empty()) { + continue; } + std::vector c_regions(region_names.size()); + for (size_t i=0; i < region_names.size(); ++i) { + c_regions[i] = const_cast(region_names[i].c_str()); + } + hts_itr_t *iter = sam_itr_regarray(index, hdr_ptr, &c_regions[0], c_regions.size()); + region_iters.push_back(iter); + file_ptrs.push_back(p->bams[j]); } - auto compare = [](bam1_t* a, bam1_t* b) -> bool { - if (a->core.tid < b->core.tid) { + + struct qItem { + Segs::Align align; + htsFile* file_ptr; + hts_itr_t* bam_iter; + size_t from; + }; + auto compare = [](qItem& a, qItem& b) -> bool { + if (a.align.delegate->core.tid > b.align.delegate->core.tid) { return true; - } else if (a->core.tid == b->core.tid) { - return a->core.pos <= b->core.pos; - } else { - return false; + } else if (a.align.delegate->core.tid == b.align.delegate->core.tid) { + return a.align.delegate->core.pos > b.align.delegate->core.pos; } + return false; }; - std::priority_queue, decltype(compare)> align_q(compare); - - while (region_iters.size() > 0) { - for (size_t i=0; i < region_iters.size(); ++i) { - bam1_t* a = bam_init1(); - if (sam_itr_next(file_ptrs[i], region_iters[i], a) >= 0) { - align_q.push(a); - } else { - region_iters.erase(region_iters.begin() + i); - file_ptrs.erase(file_ptrs.begin() + i); - bam_destroy1(a); + // sort using priority queue + std::priority_queue, decltype(compare)> pq(compare); + for (size_t i=0; i < region_iters.size(); ++i) { + bam1_t* a = bam_init1(); + if (sam_itr_next(file_ptrs[i], region_iters[i], a) >= 0) { + Segs::Align alignment = Segs::Align(a); + Segs::align_init(&alignment); + pq.push({std::move(alignment), file_ptrs[i], region_iters[i], i}); + } else { + bam_destroy1(a); + } + } + // process queue and write to bam + int count = 0; + bool filter_reads = !p->filters.empty(); + while (!pq.empty()) { + qItem item = pq.top(); // take copy + if (filter_reads) { + bool good = true; + for (auto &f: p->filters) { + if (!f.eval(item.align, hdr, -1, -1)) { + good = false; + break; + } + } + if (good) { + res = sam_write1(h_out, hdr, item.align.delegate); + if (res < 0) { + out << termcolor::red << "Error:" << termcolor::reset << " Write alignment failed" << std::endl; + break; + } + } + } else { + res = sam_write1(h_out, hdr, item.align.delegate); + if (res < 0) { + out << termcolor::red << "Error:" << termcolor::reset << " Write alignment failed" << std::endl; break; } } - + if (sam_itr_next(item.file_ptr, item.bam_iter, item.align.delegate) >= 0) { + Segs::align_init(&item.align); + pq.push(item); + } else { + bam_destroy1(item.align.delegate); + } + pq.pop(); + count += 1; } - - for (auto & item : region_iters) { - bam_itr_destroy(item); + // clean up + for (size_t i=0; i < region_iters.size(); ++i) { + bam_itr_destroy(region_iters[i]); } - -// -// bam1_t* b = bam_init1(); -// kstring_t kstr = {0, 0, nullptr}; -// std::string& stdStr = p->selectedAlign; -// kstr.l = stdStr.size(); -// kstr.m = stdStr.size() + 1; -// kstr.s = (char*)malloc(kstr.m); -// memcpy(kstr.s, stdStr.data(), stdStr.size()); -// kstr.s[kstr.l] = '\0'; -// std::cerr << kstr.s << std::endl; -// res = sam_parse1(&kstr, hdr, b); -// free(kstr.s); -// if (res < 0) { -// out << termcolor::red << "Error:" << termcolor::reset << " Could not convert str to bam1_t\n"; -// bam_destroy1(b); -// sam_close(h_out); -// return Err::NONE; -// } -// if (!write_cram) { -// hts_set_fai_filename(h_out, p->reference.c_str()); -// hts_set_threads(h_out, p->opts.threads); -// } else { -// cram_set_option(fc, CRAM_OPT_REFERENCE, p->fai); -// cram_set_option(fc, CRAM_OPT_NTHREADS, p->opts.threads); -// } -// res = sam_write1(h_out, hdr, b); -// if (res < 0) { -// out << termcolor::red << "Error:" << termcolor::reset << "Write failed\n"; -// } -// bam_destroy1(b); -// sam_close(h_out); - - - // targets is rows and columns + if (!pq.empty()) { + while (!pq.empty()) { + bam_destroy1(pq.top().align.delegate); + pq.pop(); + } + hts_close(h_out); + return Err::UNKNOWN; + } + if (!idx_path.empty()) { + res = sam_idx_save(h_out); + if (res < 0) { + out << termcolor::red << "Error:" << termcolor::reset << " Write index failed" << std::endl; + } + } + hts_close(h_out); + out << count << " alignments written\n"; return Err::NONE; } @@ -1010,7 +1008,7 @@ namespace Commands { if (parts[1] == ">") { write_bam(p, parts.back(), targets, out); } else if (parts[1] == ">>") { - write_bam(p, parts.back(), targets, out, true); + return Err::OPTION_NOT_SUPPORTED; } else { return Err::OPTION_NOT_UNDERSTOOD; } @@ -1018,8 +1016,15 @@ namespace Commands { return Err::OPTION_NOT_UNDERSTOOD; } } + return Err::NONE; + } - + Err load_file(Plot* p, std::vector parts) { + if (parts.size() != 2) { + return Err::OPTION_NOT_UNDERSTOOD; + } + std::string filename = Parse::tilde_to_home(parts.back()); + p->addTrack(filename, true); return Err::NONE; } @@ -1094,6 +1099,9 @@ namespace Commands { case BAD_REGION: out << termcolor::red << "Error:" << termcolor::reset << " Region not understood\n"; break; + case OPTION_NOT_SUPPORTED: + out << termcolor::red << "Error:" << termcolor::reset << " Option not supported\n"; + break; case OPTION_NOT_UNDERSTOOD: out << termcolor::red << "Error:" << termcolor::reset << " Option not understood\n"; break; @@ -1149,6 +1157,7 @@ namespace Commands { {"log2-cov", PARAMS { return log2_cov(p); }}, {"expand-tracks", PARAMS { return expand_tracks(p); }}, {"tlen-y", PARAMS { return tlen_y(p); }}, + {"sam", PARAMS { return sam(p, command, parts, out); }}, {"h", PARAMS { return getHelp(p, command, parts, out); }}, {"help", PARAMS { return getHelp(p, command, parts, out); }}, @@ -1156,11 +1165,13 @@ namespace Commands { {"link", PARAMS { return link(p, command, parts); }}, {"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); }}, + {"count", PARAMS { return count(p, command, out); }}, {"filter", PARAMS { return addFilter(p, command, out); }}, {"tags", PARAMS { return tags(p, command, out); }}, {"mate", PARAMS { return mate(p, command, out); }}, - {"save", PARAMS { return save_command(p, command, parts, out); }}, + {"f", PARAMS { return findRead(p, parts, out); }}, {"find", PARAMS { return findRead(p, parts, out); }}, {"ylim", PARAMS { return setYlim(p, parts, out); }}, @@ -1170,12 +1181,13 @@ namespace Commands { {"cov", PARAMS { return cov(p, parts, out); }}, {"theme", PARAMS { return theme(p, parts, out); }}, {"goto", PARAMS { return goto_command(p, parts); }}, - {"grid", PARAMS { return grid(p, parts); }}, {"add", PARAMS { return add_region(p, parts, out); }}, {"s", PARAMS { return snapshot(p, parts, out); }}, {"snapshot", PARAMS { return snapshot(p, parts, out); }}, {"online", PARAMS { return online(p, parts, out); }}, + {"grid", PARAMS { return grid(p, parts); }}, + {"load", PARAMS { return load_file(p, parts); }}, }; diff --git a/src/plot_controls.cpp b/src/plot_controls.cpp index 2dca647..203926d 100644 --- a/src/plot_controls.cpp +++ b/src/plot_controls.cpp @@ -273,7 +273,7 @@ namespace Manager { } } } else { - if (key == GLFW_KEY_ENTER || key == GLFW_KEY_KP_ENTER || key == GLFW_KEY_SPACE) { + if (key == GLFW_KEY_ENTER || key == GLFW_KEY_KP_ENTER ) { // || key == GLFW_KEY_SPACE) { inputText = Menu::commandToolTip[commandToolTipIndex]; charIndex = (int)inputText.size(); if (std::find( Menu::exec.begin(), Menu::exec.end(), inputText) != Menu::exec.end() || (inputText == "online" && !opts.genome_tag.empty())) { @@ -349,11 +349,6 @@ namespace Manager { } } const char *letter = glfwGetKeyName(key, scancode); -// std::string letter_str(letter); -// if (letter_str == "\\") { -// std::cout << "yep\n"; -// } - if (letter || key == GLFW_KEY_SPACE) { if (key == GLFW_KEY_SPACE) { // deal with special keys first Term::editInputText(inputText, " ", charIndex); @@ -379,6 +374,11 @@ namespace Manager { } else { commandToolTipIndex = std::max(commandToolTipIndex - 1, tip_bounds.lower); } + if (Utils::startsWith(inputText, "load ") && !(inputText == "load ")) { + Term::clearLine(out); + Parse::tryTabCompletion(inputText, out); + return key; + } return GLFW_KEY_UNKNOWN; } else if (key == GLFW_KEY_UP) { if (commandToolTipIndex < 0 || commandToolTipIndex >= tip_bounds.upper) { diff --git a/src/term_out.cpp b/src/term_out.cpp index 5e66e77..2c2a268 100644 --- a/src/term_out.cpp +++ b/src/term_out.cpp @@ -34,7 +34,6 @@ namespace Term { out << termcolor::underline << "\nCommand Modifier Description \n" << termcolor::reset; out << termcolor::green << "[locus] " << termcolor::reset << "e.g. 'chr1' or 'chr1:1-20000'\n"; out << termcolor::green << "add region(s) " << termcolor::reset << "Add one or more regions e.g. 'add chr1:1-20000'\n"; -// out << termcolor::green << "config " << termcolor::reset << "Opens .gw.ini config in a text editor\n"; out << termcolor::green << "count expression? " << termcolor::reset << "Count reads. See filter for example expressions'\n"; out << termcolor::green << "cov value? " << termcolor::reset << "Change max coverage value. Use 'cov' to toggle coverage\n"; out << termcolor::green << "edges " << termcolor::reset << "Toggle edges\n"; @@ -47,6 +46,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 << "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"; @@ -136,6 +136,19 @@ namespace Term { out << " Toggle line.\n A vertical line will turn on/off.\n\n"; } 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" + " 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" + " 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" + "\n\n"; } else if (s == "log2-cov") { out << " Toggle log2-coverage.\n The coverage track will be scaled by log2.\n\n"; } else if (s == "mate") { @@ -152,16 +165,20 @@ namespace Term { 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" - " The filepath extension you use will determine the output file type.\n\n" + " The filepath extension will determine the output file type.\n\n" " Examples:\n" - " 'save reads.bam' # Save all visible reads to reads.bam file. Any filters are applied.\n" - " 'save reads.cram' # Reads saved in cram format, the loaded reference genome to configure\n" - " 'save reads.sam' # Reads saved in sam format (human readable)\n" - " 'save view.png' # The current view is saved to view.png. Same functionality as 'snapshot' command\n" - " 'save session.xml' # The current session will be saved, allowing this session to be revisited\n\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.cram' # Reads saved in cram format\n" + " 'save reads.sam' # Reads saved in sam format (human readable)\n" + " 'save view.png' # The current view is saved to view.png. Same functionality as 'snapshot'\n" + " 'save session.xml' # The current session will be saved, allowing this session to be revisited\n\n" " Notes:\n" - " If multiple bams are open, reads are saved in sorted order. Some issues may arise if headers\n" - " are different between different input files\n\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" + " are incompatible.\n" + " If two regions overlap, then reads in both regions are only written once." + "\n\n"; } else if (s == "snapshot" || s == "s") { out << " Save an image of the screen.\n" " Saves current window. If no name is provided, the image name will be 'chrom_start_end.png', \n" From 357d9b319010d1f3d931435d251f1c7a8f0beb5c Mon Sep 17 00:00:00 2001 From: kcleal Date: Fri, 17 May 2024 16:13:42 +0100 Subject: [PATCH 33/51] Work on tab completion --- src/parser.cpp | 49 +++++++++++++++++++++++++++++++++---------- src/parser.h | 2 +- src/plot_controls.cpp | 16 +++++++------- 3 files changed, 48 insertions(+), 19 deletions(-) diff --git a/src/parser.cpp b/src/parser.cpp index 6b669b6..4a6dbb6 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -933,10 +933,10 @@ namespace Parse { } std::string tilde_to_home(std::string fpath) { - if (Utils::startsWith(fpath, "./")) { - fpath.erase(0, 2); - return fpath; - } +// if (Utils::startsWith(fpath, "./")) { +// fpath.erase(0, 2); +// return fpath; +// } if (!Utils::startsWith(fpath, "~/")) { return fpath; } @@ -959,24 +959,50 @@ namespace Parse { return stringpath; } - void tryTabCompletion(std::string &inputText, std::ostream& out) { + std::string longestCommonPrefix(std::vector ar) { + if (ar.empty()) + return ""; + if (ar.size() == 1) + return ar[0]; + std::sort(ar.begin(), ar.end()); + std::string first = ar.front(), last = ar.back(); + int end = std::min(first.size(), last.size()); + int i = 0; + while (i < end && first[i] == last[i]) + ++i; + std::string pre = first.substr(0, i); + return pre; + } + + void tryTabCompletion(std::string &inputText, std::ostream& out, int& charIndex) { std::vector parts = Utils::split(inputText, ' '); std::string globstr; - parts.back() = tilde_to_home(parts.back()); - if (parts.back() == "." || parts.back() == "./") { - globstr = "*"; + if (parts.back() == "./") { + globstr = "./*"; } else { globstr = parts.back() + "*"; } - size_t width = (size_t)Utils::get_terminal_width(); + parts.back() = tilde_to_home(parts.back()); std::vector glob_paths = glob_cpp::glob(globstr); if (glob_paths.size() == 1) { inputText = parts[0] + " " + glob_paths[0].generic_string(); + charIndex = inputText.size(); return; } - size_t i = 0; + std::vector path_str; for (auto &item : glob_paths) { - std::string s = item.filename().generic_string(); + path_str.push_back(item.generic_string()); + } + std::string lcp = longestCommonPrefix(path_str); + if (lcp.empty()) { + return; + } + inputText = parts[0] + " " + lcp; + charIndex = inputText.size(); + size_t width = (size_t)Utils::get_terminal_width() - 1; + size_t i = 0; + for (auto s_path : glob_paths) { + std::string s = s_path.filename().generic_string(); if (s.size() < width) { out << s; width -= s.size(); @@ -995,5 +1021,6 @@ namespace Parse { } } out.flush(); + return; } } diff --git a/src/parser.h b/src/parser.h index 3d59c71..f89789a 100644 --- a/src/parser.h +++ b/src/parser.h @@ -121,7 +121,7 @@ namespace Parse { std::string tilde_to_home(std::string fpath); - void tryTabCompletion(std::string &inputText, std::ostream& out); + void tryTabCompletion(std::string &inputText, std::ostream& out, int& charIndex); } diff --git a/src/plot_controls.cpp b/src/plot_controls.cpp index 203926d..d028816 100644 --- a/src/plot_controls.cpp +++ b/src/plot_controls.cpp @@ -243,12 +243,13 @@ namespace Manager { } const bool no_command_selected = commandToolTipIndex == -1; +// std::cerr << Utils::startsWith() << std::endl; if (no_command_selected) { if (key == GLFW_KEY_ENTER || key == GLFW_KEY_KP_ENTER) { captureText = false; processText = true; shiftPress = false; - redraw = true; + redraw = true; // todo set to false here? processed = true; commandToolTipIndex = -1; out << "\n"; @@ -273,7 +274,7 @@ namespace Manager { } } } else { - if (key == GLFW_KEY_ENTER || key == GLFW_KEY_KP_ENTER ) { // || key == GLFW_KEY_SPACE) { + if (key == GLFW_KEY_ENTER || key == GLFW_KEY_KP_ENTER ) { inputText = Menu::commandToolTip[commandToolTipIndex]; charIndex = (int)inputText.size(); if (std::find( Menu::exec.begin(), Menu::exec.end(), inputText) != Menu::exec.end() || (inputText == "online" && !opts.genome_tag.empty())) { @@ -369,16 +370,17 @@ namespace Manager { // determine which command prefix the user has typed TipBounds tip_bounds = getToolTipBounds(inputText); if (key == GLFW_KEY_TAB || key == GLFW_KEY_DOWN) { + if (Utils::startsWith(inputText, "load ") && !(inputText == "load ")) { + Term::clearLine(out); + Parse::tryTabCompletion(inputText, out, charIndex); +// commandToolTipIndex = -1; + return GLFW_KEY_UNKNOWN; + } if (commandToolTipIndex <= 0 || commandToolTipIndex <= tip_bounds.lower) { commandToolTipIndex = tip_bounds.upper; } else { commandToolTipIndex = std::max(commandToolTipIndex - 1, tip_bounds.lower); } - if (Utils::startsWith(inputText, "load ") && !(inputText == "load ")) { - Term::clearLine(out); - Parse::tryTabCompletion(inputText, out); - return key; - } return GLFW_KEY_UNKNOWN; } else if (key == GLFW_KEY_UP) { if (commandToolTipIndex < 0 || commandToolTipIndex >= tip_bounds.upper) { From 356ee512ad383c0552e1265bb7c3cdfc44f1c99a Mon Sep 17 00:00:00 2001 From: kcleal Date: Tue, 4 Jun 2024 15:52:39 +0100 Subject: [PATCH 34/51] Work on session files --- .gw.ini | 3 +- src/main.cpp | 69 +++++++++----- src/menu.cpp | 3 +- src/plot_commands.cpp | 27 +++--- src/plot_manager.cpp | 103 +++++++++++++++++--- src/plot_manager.h | 5 +- src/themes.cpp | 213 +++++++++++++++++++++++++++++++++++++++--- src/themes.h | 11 ++- src/utils.cpp | 17 +++- src/utils.h | 5 + 10 files changed, 376 insertions(+), 80 deletions(-) diff --git a/.gw.ini b/.gw.ini index 49180f5..01357d7 100644 --- a/.gw.ini +++ b/.gw.ini @@ -37,12 +37,13 @@ tabix_track_height=0.3 font=Menlo font_size=14 sv_arcs=true +session_file= [view_thresholds] soft_clip=20000 small_indel=100000 snp=500000 -edge_highlights=100000 +edge_highlights=1000000 variant_distance=100000 low_memory=1500000 diff --git a/src/main.cpp b/src/main.cpp index ee41f1b..67afec5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -76,6 +76,7 @@ int main(int argc, char *argv[]) { if (!success) { } + bool have_session_file = !iopts.session_file.empty(); static const std::vector img_fmt = { "png", "pdf", "svg" }; static const std::vector img_themes = { "igv", "dark", "slate" }; @@ -211,6 +212,8 @@ int main(int argc, char *argv[]) { .default_value(false).implicit_value(true) .help("Display path of loaded .gw.ini config"); + bool show_banner = true; + // check input for errors and merge input options with IniOptions try { program.parse_args(argc, argv); @@ -225,7 +228,10 @@ int main(int argc, char *argv[]) { return 0; } + std::vector bam_paths; + std::vector tracks; std::vector regions; + if (program.is_used("-r")) { std::vector regions_str; regions_str = program.get>("-r"); @@ -234,8 +240,6 @@ int main(int argc, char *argv[]) { } } - std::vector bam_paths; - // check if bam/cram file provided as main argument auto genome = program.get("genome"); if (Utils::endsWith(genome, ".bam") || Utils::endsWith(genome, ".cram")) { @@ -245,8 +249,6 @@ int main(int argc, char *argv[]) { } } - bool show_banner = true; - if (iopts.myIni["genomes"].has(genome)) { iopts.genome_tag = genome; genome = iopts.myIni["genomes"][genome]; @@ -254,7 +256,8 @@ int main(int argc, char *argv[]) { // prompt for genome print_banner(); show_banner = false; - std::cout << "\n Reference genomes listed in " << iopts.ini_path << std::endl << std::endl; + + std::cout << "\nReference genomes listed in " << iopts.ini_path << std::endl << std::endl; std::string online = "https://github.com/kcleal/ref_genomes/releases/download/v0.1.0"; #if defined(_WIN32) || defined(_WIN64) || defined(__MSYS__) const char *block_char = "*"; @@ -264,7 +267,7 @@ int main(int argc, char *argv[]) { std::cout << " " << block_char << " " << online << std::endl << std::endl; int i = 0; int tag_wd = 11; - std::vector vals; + std::vector> vals; #if defined(_WIN32) || defined(_WIN64) || defined(__MSYS__) std::cout << " Number | Genome-tag | Path \n"; @@ -297,32 +300,51 @@ int main(int argc, char *argv[]) { } else { std::cout << g_path << std::endl; } - vals.push_back(rg.second); + vals.push_back(rg); //rg.second); i += 1; } - if (i == 0) { + if (i == 0 && !have_session_file) { std::cerr << "No genomes listed, finishing\n"; std::exit(0); } - std::cout << "\n Enter number: " << std::flush; - int user_i; - std::cin >> user_i; - std::cerr << std::endl; - assert (user_i >= 0 && (int)user_i < vals.size()); - try { - genome = vals[user_i]; - } catch (...) { - std::cerr << "Something went wrong\n"; - std::exit(-1); + user_prompt: + if (have_session_file) { + std::cout << "\nPress ENTER to load previous session or input a genome number: " << std::flush; + + } else { + std::cout << "\nEnter genome number: " << std::flush; + } + + std::string user_input; + std::getline(std::cin, user_input); + size_t user_i = 0; + if (user_input == "q" || user_input == "quit" || user_input == "exit") { + std::exit(0); + } + if (user_input.empty()) { + if (have_session_file) { +// load_from_session(bam_paths, tracks, regions, iopts, genome); + } else { + goto user_prompt; + } + } else { + try { + user_i = std::stoi(user_input); + genome = vals[user_i].second; + iopts.genome_tag = vals[user_i].first; + std::cout << "Genome: " << iopts.genome_tag << std::endl; + } catch (...) { + goto user_prompt; + } + if (user_i < 0 || user_i > vals.size() -1) { + goto user_prompt; + } } - assert (Utils::is_file_exist(genome)); - iopts.genome_tag = genome; } else if (!genome.empty() && !Utils::is_file_exist(genome)) { std::cerr << "Loading remote genome" << std::endl; } - std::vector tracks; if (program.is_used("--track")) { tracks = program.get>("--track"); for (auto &trk: tracks){ @@ -351,7 +373,7 @@ int main(int argc, char *argv[]) { } } } - if (!program.is_used("genome") && genome.empty() && !bam_paths.empty()) { + if (!have_session_file && !program.is_used("genome") && genome.empty() && !bam_paths.empty()) { HGW::guessRefGenomeFromBam(genome, iopts, bam_paths, regions); if (genome.empty()) { std::exit(0); @@ -459,9 +481,6 @@ int main(int argc, char *argv[]) { if (program.is_used("--no-soft-clips")) { iopts.soft_clip_threshold = 0; } -// if (program.is_used("--low-mem")) { -// iopts.low_mem = true; -// } if (program.is_used("--start-index")) { iopts.start_index = program.get("--start-index"); } diff --git a/src/menu.cpp b/src/menu.cpp index d5837d0..2849476 100644 --- a/src/menu.cpp +++ b/src/menu.cpp @@ -457,8 +457,7 @@ namespace Menu { opts.control_level = "close"; opts.menu_table = Themes::MenuTable::MAIN; opts.previous_level = opts.menu_level; - mINI::INIFile file(opts.ini_path); - file.write(opts.myIni); + opts.saveIniChanges(); std::cout << "Saved .gw.ini to " << opts.ini_path << std::endl; return false; } else if (opts.control_level == "delete") { diff --git a/src/plot_commands.cpp b/src/plot_commands.cpp index 02c12fa..b1a3eb3 100644 --- a/src/plot_commands.cpp +++ b/src/plot_commands.cpp @@ -304,15 +304,18 @@ namespace Commands { Err link(Plot* p, std::string& command, std::vector& parts) { bool relink = false; if (command == "link" || command == "link all") { - relink = (p->opts.link_op != 2) ? true : false; + relink = p->opts.link_op != 2; p->opts.link_op = 2; + p->opts.link = "all"; } else if (parts.size() == 2) { if (parts[1] == "sv") { - relink = (p->opts.link_op != 1) ? true : false; + relink = p->opts.link_op != 1; p->opts.link_op = 1; + p->opts.link = "sv"; } else if (parts[1] == "none") { - relink = (p->opts.link_op != 0) ? true : false; + relink = p->opts.link_op != 0; p->opts.link_op = 0; + p->opts.link = "none"; } } if (relink) { @@ -509,16 +512,13 @@ namespace Commands { } Err setYlim(Plot* p, std::vector parts, std::ostream& out) { - int ylim = p->opts.ylim; - int samMaxY = p->opts.ylim; - int max_tlen = p->opts.max_tlen; try { if (!p->opts.tlen_yscale) { - ylim = std::stoi(parts.back()); - samMaxY = p->opts.ylim; + p->opts.ylim = std::stoi(parts.back()); + p->samMaxY = p->opts.ylim; } else { - max_tlen = std::stoi(parts.back()); - samMaxY = p->opts.max_tlen; + p->opts.max_tlen = std::stoi(parts.back()); + p->samMaxY = p->opts.max_tlen; } } catch (...) { out << termcolor::red << "Error:" << termcolor::reset << " ylim invalid value\n"; @@ -528,14 +528,11 @@ namespace Commands { HGW::refreshLinked(p->collections, p->opts, &p->samMaxY); p->processed = true; p->redraw = true; - p->opts.ylim = ylim; - p->samMaxY = samMaxY; - p->opts.max_tlen = max_tlen; return Err::NONE; } Err indelLength(Plot* p, std::vector parts, std::ostream& out) { - int indel_length = p->opts.indel_length; + int indel_length; try { indel_length = std::stoi(parts.back()); } catch (...) { @@ -1039,7 +1036,7 @@ namespace Commands { if (reason == Err::NONE) { int res = faidx_has_seq(p->fai, rgn.chrom.c_str()); if (res <= 0) { - reason = Err::CHROM_NOT_IN_REFERENCE; + return Err::OPTION_NOT_UNDERSTOOD; } if (p->mode != Manager::Show::SINGLE) { p->mode = Manager::Show::SINGLE; } if (p->regions.empty()) { diff --git a/src/plot_manager.cpp b/src/plot_manager.cpp index 4c6d3e0..b3526e2 100644 --- a/src/plot_manager.cpp +++ b/src/plot_manager.cpp @@ -171,6 +171,7 @@ namespace Manager { this->pixelMemory.resize(size); this->rasterSurface = SkSurface::MakeRasterDirect( info, &pixelMemory[0], rowBytes); + rasterCanvas = rasterSurface->getCanvas(); return pixelMemory.size(); } @@ -420,11 +421,75 @@ namespace Manager { } } + void GwPlot::load_session() { + mINI::INIFile file(opts.session_file); + file.read(opts. seshIni); + if (!opts.seshIni.has("data")) { + std::cerr << "Error: session file is missing 'data' heading. Invalid session file\n"; + std::exit(-1); + } + reference = opts.seshIni["data"]["genome_path"]; + opts.genome_tag = opts.seshIni["data"]["genome_tag"]; + opts.getOptionsFromSessionIni(opts.seshIni); + if (opts.seshIni["data"].has("mode")) { + mode = (opts.seshIni["data"]["mode"] == "tiled") ? Show::TILED : Show::SINGLE; + } + if (!reference.empty()) { + fai = fai_load(reference.c_str()); + if (fai == nullptr) { + std::cerr << "Error: reference genome could not be opened " << reference << std::endl; + std::exit(-1); + } + std::cout << "Genome: " << reference << std::endl; + } + + 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, "bam")) { + bam_paths.push_back(item.second); + } else if (Utils::startsWith(item.first, "track")) { + tracks.push_back(HGW::GwTrack()); + tracks.back().open(item.second, true); + tracks.back().variant_distance = &opts.variant_distance; + } else if (Utils::startsWith(item.first, "region")) { + std::string rgn = item.second; + regions.push_back(Utils::parseRegion(rgn)); + } else if (Utils::startsWith(item.first, "var")) { + std::string v = item.second; + addVariantTrack(v, 0, false, false); + } + } + + fonts.setTypeface(opts.font_str, opts.font_size); + + for (auto &fn: bam_paths) { + 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()); + indexes.push_back(idx); + } + + if (opts.seshIni.has("commands")) { + for (auto &item: opts.seshIni["commands"]) { + inputText = item.second; + commandProcessed(); + } + } + } + int GwPlot::startUI(GrDirectContext* sContext, SkSurface *sSurface, int delay) { + if (!opts.session_file.empty() && reference.empty()) { + load_session(); + } if (terminalOutput) { - std::cerr << "Type ':help' for more info\n"; + std::cerr << "\nType" << termcolor::green << " '/help'" << termcolor::reset << " for more info\n"; } else { - outStr << "Type ':help' for more info\n"; + outStr << "Type '/help' for more info\n"; } vCursor = glfwCreateStandardCursor(GLFW_VRESIZE_CURSOR); @@ -517,7 +582,7 @@ namespace Manager { pixelMemory.resize(size); rasterSurface = SkSurface::MakeRasterDirect( info, &pixelMemory[0], rowBytes); - + rasterCanvas = rasterSurface->getCanvas(); resizeTimer = std::chrono::high_resolution_clock::now(); } @@ -528,8 +593,17 @@ namespace Manager { } saveLabels(); + std::vector track_paths; track_paths.reserve(tracks.size()); + for (const auto& item: tracks) { + track_paths.push_back(item.path); + } + std::vector variant_paths; variant_paths.reserve(variantTracks.size()); + for (const auto& item: variantTracks) { + variant_paths.push_back(item.path); + } + opts.saveCurrentSession(reference, bam_paths, track_paths, regions, variant_paths, commandHistory, "", mode); if (wasResized) { - // no idea why, but unless exit is here then we get an abort error if we return to main. Something to do with lifetime of backendRenderTarget + // no idea why, but unless exit is here then we get an abort error if we return to main. Something to do with lifetime of backendRenderTarget? if (terminalOutput) { std::cerr << "\nGw finished\n"; } @@ -723,7 +797,7 @@ namespace Manager { // std::chrono::high_resolution_clock::time_point initial = std::chrono::high_resolution_clock::now(); - SkCanvas *canvasR = rasterSurface->getCanvas(); + SkCanvas *canvasR = rasterCanvas; canvas->drawPaint(opts.theme.bgPaint); canvasR->drawPaint(opts.theme.bgPaint); @@ -1018,7 +1092,7 @@ namespace Manager { } } bool current_view_is_images = (!variantTracks.empty() && variantTracks[variantFileSelection].type == HGW::TrackType::IMAGES); - if (bams.empty() && !current_view_is_images) { + if (bams.empty() && !current_view_is_images && mode != SETTINGS) { float trackBoundary = fb_height - totalTabixY - refSpace; std::string dd_msg = "Drag-and-drop bam or cram files here"; float msg_width = fonts.overlay.measureText(dd_msg.c_str(), dd_msg.size(), SkTextEncoding::kUTF8); @@ -1031,7 +1105,7 @@ namespace Manager { if (trackBoundary > (float)fb_height / 2) { canvas->drawTextBlob(blob.get(), txt_start, (float)fb_height / 2, tcMenu); } - } else if (regions.empty() && !current_view_is_images) { + } else if (regions.empty() && !current_view_is_images && mode != SETTINGS) { std::string dd_msg = "Type e.g. '/chr1' to add a region, or drag-and-drop a vcf file here"; float msg_width = fonts.overlay.measureText(dd_msg.c_str(), dd_msg.size(), SkTextEncoding::kUTF8); float txt_start = ((float)fb_width / 2) - (msg_width / 2); @@ -1051,13 +1125,13 @@ namespace Manager { int endIdx = bStart + bLen; currentVarTrack->iterateToIndex(endIdx); for (int i=bStart; imultiRegions.size() && !bams.empty()) { regions = currentVarTrack->multiRegions[i]; - runDraw(); + runDrawOnCanvas(canvas); + sContext->flush(); sk_sp img(sSurface->makeImageSnapshot()); imageCache[i] = img; - sContext->flush(); } } } @@ -1072,7 +1146,7 @@ namespace Manager { [&](const int a, const int b) { for (int i=a; i data(nullptr); @@ -1115,7 +1189,6 @@ namespace Manager { setScaling(); float y_gap = (variantTracks.size() <= 1) ? 0 : (10 * monitorScale); bboxes = Utils::imageBoundingBoxes(opts.number, fb_width, fb_height, 15, 15, y_gap); - if (currentVarTrack->image_glob.empty()) { tileDrawingThread(canvas, sContext, sSurface); // draws images from variant file } else { @@ -1142,7 +1215,7 @@ namespace Manager { int i = bStart; for (auto &b : bboxes) { SkRect rect; - if (imageCache.contains(i)) { + if (imageCache.find(i) != imageCache.end()) { int w = imageCache[i]->width(); int h = imageCache[i]->height(); float ratio = (float)w / (float)h; @@ -1193,7 +1266,7 @@ namespace Manager { } void GwPlot::runDraw() { - runDrawOnCanvas(rasterSurface->getCanvas()); + runDrawOnCanvas(rasterCanvas); } void GwPlot::runDrawNoBuffer() { @@ -1203,7 +1276,7 @@ namespace Manager { if (bams.empty()) { return; } - SkCanvas* canvas = rasterSurface->getCanvas(); + SkCanvas* canvas = rasterCanvas; canvas->drawPaint(opts.theme.bgPaint); fetchRefSeqs(); diff --git a/src/plot_manager.h b/src/plot_manager.h index dec1360..3a3915a 100644 --- a/src/plot_manager.h +++ b/src/plot_manager.h @@ -118,7 +118,7 @@ namespace Manager { std::vector filters; - ankerl::unordered_dense::map< int, sk_sp> imageCache; + std::unordered_map< int, sk_sp> imageCache; // keys are variantFilename and variantId ankerl::unordered_dense::map< std::string, ankerl::unordered_dense::map< std::string, Utils::Label>> inputLabels; @@ -134,6 +134,7 @@ namespace Manager { GLFWwindow* backWindow; sk_sp rasterSurface; + SkCanvas* rasterCanvas; Show mode; Show last_mode; @@ -184,6 +185,8 @@ namespace Manager { void setVariantSite(std::string &chrom, long start, std::string &chrom2, long stop); + void load_session(); + int startUI(GrDirectContext* sContext, SkSurface *sSurface, int delay); void keyPress(int key, int scancode, int action, int mods); diff --git a/src/themes.cpp b/src/themes.cpp index 6314c9f..31df257 100644 --- a/src/themes.cpp +++ b/src/themes.cpp @@ -417,28 +417,19 @@ namespace Themes { Keys::getKeyTable(key_table); theme_str = myIni["general"]["theme"]; - if (theme_str == "dark") { - theme = Themes::DarkTheme(); - } else if (theme_str == "slate") { - theme = Themes::SlateTheme(); - } else { - theme = Themes::IgvTheme(); - } + if (theme_str == "dark") { theme = Themes::DarkTheme(); } else if (theme_str == "slate") { theme = Themes::SlateTheme(); } else { theme = Themes::IgvTheme(); } dimensions_str = myIni["general"]["dimensions"]; dimensions = Utils::parseDimensions(dimensions_str); - std::string lnk = myIni["general"]["link"]; + link = myIni["general"]["link"]; link_op = 0; - if (lnk == "none") { - } else if (lnk == "sv") { + if (link == "sv") { link_op = 1; - } else if (lnk == "all") { + } else if (link == "all") { link_op = 2; } else { - std::cerr << "Link type not known [none/sv/all] " << lnk << std::endl; - std::exit(-1); + link = "none"; } - link = myIni["general"]["link"]; indel_length = std::stoi(myIni["general"]["indel_length"]); ylim = std::stoi(myIni["general"]["ylim"]); @@ -461,6 +452,9 @@ namespace Themes { if (myIni["general"].has("sv_arcs")) { sv_arcs = myIni["general"]["sv_arcs"] == "true"; } + if (myIni["general"].has("session_file")) { + session_file = myIni["general"]["session_file"]; + } soft_clip_threshold = std::stoi(myIni["view_thresholds"]["soft_clip"]); small_indel_threshold = std::stoi(myIni["view_thresholds"]["small_indel"]); @@ -524,6 +518,95 @@ namespace Themes { } } + void IniOptions::getOptionsFromSessionIni(mINI::INIStructure& sesh) { + if (sesh.has("general")) { + mINI::INIMap& sub = sesh["general"]; + if (sub.has("theme")) { + theme_str = sub["theme"]; + if (theme_str == "dark") { + theme = Themes::DarkTheme(); + } else if (theme_str == "slate") { + theme = Themes::SlateTheme(); + } else { + theme = Themes::IgvTheme(); + } + } + if (sub.has("dimensions")) { + dimensions_str = sub["dimensions"]; + dimensions = Utils::parseDimensions(dimensions_str); + } + if (sub.has("indel_length")) { + indel_length = std::stoi(sub["indel_length"]); + } + if (sub.has("ylim")) { + ylim = std::stoi(sub["ylim"]); + } + if (sub.has("coverage")) { + max_coverage = (sub["coverage"] == "true") ? 100000 : 0; + } + if (sub.has("log2_cov")) { + log2_cov = sub["log2_cov"] == "true"; + } + if (sub.has("expand_tracks")) { + expand_tracks = sub["expand_tracks"] == "true"; + } + if (sub.has("link")) { + link = sub["link"] == "true"; + link_op = 0; + if (link == "sv") { + link_op = 1; + } else if (link == "all") { + link_op = 2; + } else { + link = "none"; + } + } + if (sub.has("split_view_size")) { + split_view_size = std::stoi(sub["split_view_size"]); + } + if (sub.has("pad")) { + pad = std::stoi(sub["pad"]); + } + if (sub.has("tabix_track_height")) { + tab_track_height = std::stof(sub["tabix_track_height"]); + } + if (sub.has("font")) { + font_str = sub["font"]; + } + if (sub.has("font_size")) { + font_size = std::stoi(sub["font_size"]); + } + } + if (sesh.has("view_thresholds")) { + mINI::INIMap& vt = sesh["view_thresholds"]; + if (vt.has("soft_clip")) { + soft_clip_threshold = std::stoi(vt["soft_clip"]); + } + if (vt.has("small_indel_threshold")) { + small_indel_threshold = std::stoi(vt["small_indel_threshold"]); + } + if (vt.has("snp_threshold")) { + snp_threshold = std::stoi(vt["snp_threshold"]); + } + if (vt.has("edge_highlights")) { + edge_highlights = std::stoi(vt["edge_highlights"]); + } + } + if (sesh.has("labelling")) { + mINI::INIMap &lb = sesh["labelling"]; + if (lb.has("number")) { + number_str = lb["number"]; + number = Utils::parseDimensions(number_str); + } + if (lb.has("parse_label")) { + parse_label =lb["parse_label"]; + } + if (lb.has("labels")) { + labels = lb["labels"]; + } + } + } + std::filesystem::path IniOptions::writeDefaultIni(std::filesystem::path &homedir, std::filesystem::path &home_config, std::filesystem::path &gwIni) { std::ofstream outIni; std::filesystem::path outPath; @@ -559,6 +642,7 @@ namespace Themes { std::filesystem::path path; std::filesystem::path homedir(home); std::filesystem::path gwini(".gw.ini"); +// std::filesystem::path gw_session(".gw_session.ini"); if (std::filesystem::exists(homedir / gwini)) { path = homedir / gwini; } else { @@ -586,6 +670,107 @@ namespace Themes { return true; } + void IniOptions::saveIniChanges() { + mINI::INIFile file(ini_path); + file.write(myIni); + } + + void IniOptions::saveCurrentSession(std::string& genome_path, std::vector& bam_paths, + std::vector& track_paths, std::vector& regions, + std::vector& variant_tracks_paths, + std::vector& commands, std::string output_session, + int mode) { + + + if (output_session.empty()) { + if (session_file.empty()) { // fill new session + std::filesystem::path gwini(ini_path); + std::filesystem::path sesh(".gw_session.ini"); + std::filesystem::path sesh_path = gwini.parent_path() / sesh; + myIni["general"].set("session_file", sesh_path.string()); + saveIniChanges(); + } + output_session = myIni["general"]["session_file"]; + } + mINI::INIFile file(output_session); + + seshIni["data"]["genome_tag"] = genome_tag; + seshIni["data"]["genome_path"] = genome_path; + int count = 0; + for (const auto& item : bam_paths) { + seshIni["data"]["bam" + std::to_string(count)] = item; + count += 1; + } + count = 0; + for (const auto& item : track_paths) { + seshIni["data"]["track" + std::to_string(count)] = item; + count += 1; + } + count = 0; + for (auto& item: regions) { + seshIni["data"]["region" + std::to_string(count)] = item.toString(); + count += 1; + } + count = 0; + for (auto& item: variant_tracks_paths) { + std::filesystem::path fspath(item); + std::string path = std::filesystem::absolute(fspath).string(); + seshIni["data"]["var" + std::to_string(count)] = path; + count += 1; + } + seshIni["data"]["mode"] = (mode == 1) ? "tiled" : "single"; + count = 0; + size_t last_refresh = 0; + std::vector keep = {"egdes", "expand-tracks", "soft-clips", "sc", "mm", "mismatches", "ins", "insertions"}; + for (auto& item: commands) { + for (const auto& k: keep) { + if (Utils::startsWith(item, k)) { + seshIni["commands"][std::to_string(count)] = item; + } + } + if (item == "r" || item == "refresh") { + last_refresh = count; + } + count += 1; + } + keep = {"filter ", "find ", "f "}; + size_t j = 0; + for (; last_refresh < commands.size(); ++last_refresh) { + for (const auto& k: keep) { + if (Utils::startsWith(commands[last_refresh], k)) { + seshIni["commands"][std::to_string(j)] = commands[last_refresh]; + j++; + } + } + } + mINI::INIMap& sub = seshIni["general"]; + sub["theme"] = theme_str; + sub["dimensions"] = std::to_string(dimensions.x) + "x" + std::to_string(dimensions.y); + sub["indel_length"] = std::to_string(indel_length); + sub["ylim"] = std::to_string(ylim); + sub["coverage"] = (max_coverage) ? "true" : "false"; + sub["log2_cov"] = (log2_cov) ? "true" : "false"; + sub["expand_tracks"] = (expand_tracks) ? "true" : "false"; + sub["link"] = link; + sub["split_view_size"] = std::to_string(split_view_size); + sub["pad"] = std::to_string(pad); + sub["tabix_track_height"] = std::to_string(tab_track_height); + sub["font"] = font_str; + sub["font_size"] = std::to_string(font_size); + + mINI::INIMap& vt = seshIni["view_thresholds"]; + vt["soft_clip"] = std::to_string(soft_clip_threshold); + vt["small_indel"] = std::to_string(small_indel_threshold); + vt["snp"] = std::to_string(snp_threshold); + vt["edge_highlights"] = std::to_string(edge_highlights); + + mINI::INIMap& lb = seshIni["labelling"]; + lb["number"] = std::to_string(number.x) + "x" + std::to_string(number.y); + lb["parse_label"] = parse_label; + lb["labels"] = labels; + + file.generate(seshIni, true); + } const SkGlyphID glyphs[1] = {100}; diff --git a/src/themes.h b/src/themes.h index 0c490db..8ee721f 100644 --- a/src/themes.h +++ b/src/themes.h @@ -46,7 +46,6 @@ #include "utils.h" #include "export_definitions.h" - namespace Themes { enum MenuTable { @@ -137,11 +136,11 @@ namespace Themes { IniOptions(); ~IniOptions() {}; - mINI::INIStructure myIni; -// std::unordered_map shift_keymap; + mINI::INIStructure myIni, seshIni; std::unordered_map shift_keymap; BaseTheme theme; Utils::Dims dimensions, number; + std::string session_file; std::string genome_tag; std::string theme_str, font_str, parse_label, labels, link, dimensions_str, number_str, ini_path, outdir; std::string menu_level, control_level, previous_level; @@ -173,7 +172,13 @@ namespace Themes { bool readIni(); static std::filesystem::path writeDefaultIni(std::filesystem::path &homedir, std::filesystem::path &home_config, std::filesystem::path &gwIni); void getOptionsFromIni(); + void getOptionsFromSessionIni(mINI::INIStructure& seshIni); + void saveIniChanges(); void setTheme(std::string &theme_str); + void saveCurrentSession(std::string& genome_path, std::vector& bam_paths, + std::vector& track_paths, std::vector& regions, + std::vector& variant_tracks_paths, + std::vector& commands, std::string output_session, int mode); }; diff --git a/src/utils.cpp b/src/utils.cpp index 4306289..2939931 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -169,6 +169,14 @@ namespace Utils { start = end + 1; end = s.find(delim, start); r->end = std::stoi(s.substr(start, end - start)); + if (end != std::string::npos) { + start = end + 1; + end = s.find(delim, start); + r->markerPos = std::stoi(s.substr(start, end - start)); + start = end + 1; + end = s.find(delim, start); + r->markerPosEnd = std::stoi(s.substr(start, end - start)); + } } else { r->start = std::stoi(s.substr(start, s.size())); r->end = r->start + 1; @@ -186,9 +194,6 @@ namespace Utils { Utils::strToRegion(®, s, ','); } else if (s.find("\t") != std::string::npos) { Utils::strToRegion(®, s, '\t'); -// } -// else if (s.find("_") != std::string::npos) { -// Utils::strToRegion(®, s, '_'); } else if (s.find(" ") != std::string::npos) { Utils::strToRegion(®, s, ' '); } else { @@ -202,10 +207,14 @@ namespace Utils { if (reg.start == reg.end) { reg.end += 1; } - reg.markerPos = -1; return reg; } + std::string Region::toString() { + return chrom + ":" + std::to_string(start) + "-" + std::to_string(end) + + ((markerPos >= 0) ? ":" + std::to_string(markerPos) + ":" + std::to_string(markerPosEnd) : ""); + } + std::filesystem::path makeFilenameFromRegions(std::vector ®ions) { std::filesystem::path fname = "GW~"; for (auto &rgn: regions) { diff --git a/src/utils.h b/src/utils.h index da815e2..a2d8a7c 100644 --- a/src/utils.h +++ b/src/utils.h @@ -55,6 +55,10 @@ namespace Utils { coding_end = -1; value = 0; level = 0; + start = 0; + end = 0; + strand = 0; + anyToDraw = false; } }; @@ -82,6 +86,7 @@ namespace Utils { markerPosEnd = -1; refSeq = nullptr; } + std::string toString(); }; EXPORT Region parseRegion(std::string &r); From 546a85946de0897624299bf3e87aa4cd4171a61f Mon Sep 17 00:00:00 2001 From: kcleal Date: Fri, 7 Jun 2024 10:41:51 +0100 Subject: [PATCH 35/51] More work on session files --- src/drawing.cpp | 8 +++--- src/hts_funcs.cpp | 50 +++++++++++++++++++--------------- src/hts_funcs.h | 2 +- src/main.cpp | 10 ++++++- src/plot_manager.cpp | 64 +++++++++++++++++++++++++++++++++----------- src/plot_manager.h | 5 +++- src/segments.cpp | 33 ++++++++++++++--------- src/segments.h | 7 ++--- src/themes.cpp | 24 +++++++++++------ src/themes.h | 5 ++-- src/utils.cpp | 14 +++++++--- src/utils.h | 1 + 12 files changed, 146 insertions(+), 77 deletions(-) diff --git a/src/drawing.cpp b/src/drawing.cpp index ba9a91d..8471e51 100644 --- a/src/drawing.cpp +++ b/src/drawing.cpp @@ -689,7 +689,9 @@ namespace Drawing { SkPath path; const Themes::BaseTheme &theme = opts.theme; - std::vector text_ins, text_del; + static std::vector text_ins, text_del; + text_ins.clear(); + text_del.clear(); int regionBegin = cl.region->start; int regionEnd = cl.region->end; @@ -1163,8 +1165,7 @@ namespace Drawing { } } - void - drawLabel(const Themes::IniOptions &opts, SkCanvas *canvas, SkRect &rect, Utils::Label &label, Themes::Fonts &fonts, + void drawLabel(const Themes::IniOptions &opts, SkCanvas *canvas, SkRect &rect, Utils::Label &label, Themes::Fonts &fonts, const ankerl::unordered_dense::set &seenLabels, const std::vector &srtLabels) { float pad = 2; std::string cur = label.current(); @@ -1173,7 +1174,6 @@ namespace Drawing { } sk_sp blob = SkTextBlob::MakeFromString(cur.c_str(), fonts.overlay); float wl = fonts.overlayWidth * (cur.size() + 1); - auto it = std::find(srtLabels.begin(), srtLabels.end(), cur); int idx; if (it != srtLabels.end()) { diff --git a/src/hts_funcs.cpp b/src/hts_funcs.cpp index 7143c79..67dd14f 100644 --- a/src/hts_funcs.cpp +++ b/src/hts_funcs.cpp @@ -1192,6 +1192,7 @@ namespace HGW { void GwTrack::open(const std::string &p, bool add_to_dict=true) { fileIndex = 0; path = p; + done = false; this->add_to_dict = add_to_dict; if (Utils::endsWith(p, ".bed")) { kind = BED_NOI; @@ -1914,7 +1915,7 @@ namespace HGW { bcf_close(fp_out); } - GwVariantTrack::GwVariantTrack(std::string &path, bool cacheStdin, Themes::IniOptions *t_opts, int startIndex, + GwVariantTrack::GwVariantTrack(std::string &path, bool cacheStdin, Themes::IniOptions *t_opts, int endIndex, std::vector &t_labelChoices, std::shared_ptr< ankerl::unordered_dense::map< std::string, Utils::Label>> t_inputLabels, std::shared_ptr< ankerl::unordered_dense::set> t_seenLabels) { @@ -1947,9 +1948,10 @@ namespace HGW { vcf.label_to_parse = m_opts->parse_label.c_str(); vcf.open(path); trackDone = &vcf.done; - if (startIndex > 0) { - nextN(startIndex); + if (endIndex > 0) { + nextN(endIndex); } + blockStart = endIndex; } else if (Utils::endsWith(path, ".png") || Utils::endsWith(path, ".png'") || Utils::endsWith(path, ".png\"")) { type = IMAGES; image_glob = glob_cpp::glob(path); @@ -1972,9 +1974,10 @@ namespace HGW { variantTrack.open(path, false); trackDone = &variantTrack.done; variantTrack.fetch(nullptr); // initialize iterators - if (startIndex > 0) { - nextN(startIndex); + if (endIndex > 0) { + nextN(endIndex); } + blockStart = endIndex; } this->path = path; init = true; @@ -2022,7 +2025,7 @@ namespace HGW { } } - // gets called when new image tiles are loaded, labels are parsed from filenames if possible + // gets called when new image tiles are loaded (pngs), labels are parsed from filenames if possible // 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 @@ -2049,26 +2052,29 @@ namespace HGW { long rlen = stop - start; std::vector v; bool isTrans = chrom != chrom2; + Utils::Region* r1; Utils::Region* r2; if (!isTrans && rlen <= m_opts->split_view_size) { - Utils::Region r; v.resize(1); - v[0].chrom = chrom; - v[0].start = (1 > start - m_opts->pad) ? 1 : start - m_opts->pad; - v[0].end = stop + m_opts->pad; - v[0].markerPos = start; - v[0].markerPosEnd = stop; + r1 = & v[0]; + r1->chrom = chrom; + r1->start = (1 > start - m_opts->pad) ? 1 : start - m_opts->pad; + r1->end = stop + m_opts->pad; + r1->markerPos = start; + r1->markerPosEnd = stop; } else { v.resize(2); - v[0].chrom = chrom; - v[0].start = (1 > start - m_opts->pad) ? 1 : start - m_opts->pad; - v[0].end = start + m_opts->pad; - v[0].markerPos = start; - v[0].markerPosEnd = start; - v[1].chrom = chrom2; - v[1].start = (1 > stop - m_opts->pad) ? 1 : stop - m_opts->pad; - v[1].end = stop + m_opts->pad; - v[1].markerPos = stop; - v[1].markerPosEnd = stop; + r1 = &v[0]; + r1->chrom = chrom; + r1->start = (1 > start - m_opts->pad) ? 1 : start - m_opts->pad; + r1->end = start + m_opts->pad; + r1->markerPos = start; + r1->markerPosEnd = start; + r2 = &v[1]; + r2->chrom = chrom2; + r2->start = (1 > stop - m_opts->pad) ? 1 : stop - m_opts->pad; + r2->end = stop + m_opts->pad; + r2->markerPos = stop; + r2->markerPosEnd = stop; } multiRegions.push_back(v); if (inputLabels->contains(rid)) { diff --git a/src/hts_funcs.h b/src/hts_funcs.h index ff02c7a..27556ae 100644 --- a/src/hts_funcs.h +++ b/src/hts_funcs.h @@ -210,7 +210,7 @@ namespace HGW { }; class GwVariantTrack { public: - GwVariantTrack(std::string &path, bool cacheStdin, Themes::IniOptions *t_opts, int startIndex, + GwVariantTrack(std::string &path, bool cacheStdin, Themes::IniOptions *t_opts, int endIndex, std::vector &t_labelChoices, std::shared_ptr> t_inputLabels, std::shared_ptr> t_seenLabels); diff --git a/src/main.cpp b/src/main.cpp index 67afec5..3938cf6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -499,7 +499,15 @@ int main(int argc, char *argv[]) { } if (!iopts.no_show) { // plot something to screen - + if (have_session_file) { + mINI::INIFile file(iopts.session_file); + file.read(iopts.seshIni); + if (!iopts.seshIni.has("data") || !iopts.seshIni.has("show")) { + std::cerr << "Error: session file is missing 'data' heading. Invalid session file\n"; + std::exit(-1); + } + iopts.getOptionsFromSessionIni(iopts.seshIni); + } /* * / Gw start */ diff --git a/src/plot_manager.cpp b/src/plot_manager.cpp index b3526e2..3310dd3 100644 --- a/src/plot_manager.cpp +++ b/src/plot_manager.cpp @@ -325,11 +325,12 @@ namespace Manager { std::shared_ptr> sLabels = std::make_shared>(seenLabels[variantFilename]); variantTracks.push_back( - HGW::GwVariantTrack(path, cacheStdin, &opts, startIndex, + HGW::GwVariantTrack(path, cacheStdin, &opts, startIndex + (opts.number.x * opts.number.y), labelChoices, inLabels, sLabels) ); + std::cout << "plot_manager YO: " << variantFilename << std::endl; } void GwPlot::setOutLabelFile(const std::string &path) { @@ -421,16 +422,15 @@ namespace Manager { } } - void GwPlot::load_session() { + void GwPlot::loadSession() { mINI::INIFile file(opts.session_file); - file.read(opts. seshIni); - if (!opts.seshIni.has("data")) { + file.read(opts.seshIni); + if (!opts.seshIni.has("data") || !opts.seshIni.has("show")) { std::cerr << "Error: session file is missing 'data' heading. Invalid session file\n"; std::exit(-1); } reference = opts.seshIni["data"]["genome_path"]; opts.genome_tag = opts.seshIni["data"]["genome_tag"]; - opts.getOptionsFromSessionIni(opts.seshIni); if (opts.seshIni["data"].has("mode")) { mode = (opts.seshIni["data"]["mode"] == "tiled") ? Show::TILED : Show::SINGLE; } @@ -443,6 +443,11 @@ namespace Manager { std::cout << "Genome: " << reference << std::endl; } + if (opts.seshIni.has("labelling") && opts.seshIni["labelling"].has("labels")) { + std::vector labels = Utils::split_keep_empty_str(opts.seshIni["labelling"]["labels"], ','); + 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"]) { @@ -457,7 +462,20 @@ namespace Manager { regions.push_back(Utils::parseRegion(rgn)); } else if (Utils::startsWith(item.first, "var")) { std::string v = item.second; - addVariantTrack(v, 0, false, false); + addVariantTrack(v, 0, false, true); + } + } + size_t count = 0; + for (const auto& item : opts.seshIni["show"]) { + if (Utils::startsWith(item.first, "region")) { + std::string rgn = item.second; + regions.push_back(Utils::parseRegion(rgn)); + } else if (Utils::startsWith(item.first, "mode")) { + mode = (item.second == "tiled") ? Show::TILED : Show::SINGLE; + } else if (Utils::startsWith(item.first, "var") && count < variantTracks.size()) { + variantTracks[count].iterateToIndex(std::stoi(item.second) + (opts.number.x * opts.number.y)); + variantTracks[count].blockStart = std::stoi(item.second); + count += 1; } } @@ -480,11 +498,32 @@ namespace Manager { commandProcessed(); } } + if (opts.seshIni["show"].has("window_position")) { + Utils::Dims pos = Utils::parseDimensions(opts.seshIni["show"]["window_position"]); + glfwSetWindowPos(window, pos.x, pos.y); + } + glfwSetWindowSize(window, opts.dimensions.x, opts.dimensions.y); + windowResize(opts.dimensions.x, opts.dimensions.y); + redraw = true; + } + + void GwPlot::saveSession() { + std::vector track_paths; + for (const auto& item: tracks) { + track_paths.push_back(item.path); + } + std::vector> variant_paths_info; + for (const auto& item: variantTracks) { + variant_paths_info.push_back({item.path, item.blockStart}); + } + int xpos, ypos; + glfwGetWindowPos(window, &xpos, &ypos); + opts.saveCurrentSession(reference, bam_paths, track_paths, regions, variant_paths_info, commandHistory, "", mode, xpos, ypos); } int GwPlot::startUI(GrDirectContext* sContext, SkSurface *sSurface, int delay) { if (!opts.session_file.empty() && reference.empty()) { - load_session(); + loadSession(); } if (terminalOutput) { std::cerr << "\nType" << termcolor::green << " '/help'" << termcolor::reset << " for more info\n"; @@ -593,15 +632,7 @@ namespace Manager { } saveLabels(); - std::vector track_paths; track_paths.reserve(tracks.size()); - for (const auto& item: tracks) { - track_paths.push_back(item.path); - } - std::vector variant_paths; variant_paths.reserve(variantTracks.size()); - for (const auto& item: variantTracks) { - variant_paths.push_back(item.path); - } - opts.saveCurrentSession(reference, bam_paths, track_paths, regions, variant_paths, commandHistory, "", mode); + saveSession(); if (wasResized) { // no idea why, but unless exit is here then we get an abort error if we return to main. Something to do with lifetime of backendRenderTarget? if (terminalOutput) { @@ -791,6 +822,7 @@ namespace Manager { } else if (opts.tlen_yscale) { pH = std::fmax(pH, 8); } + minGapSize = (uint32_t)(fb_width * 0.005); } void GwPlot::drawScreen(SkCanvas* canvas, GrDirectContext* sContext, SkSurface *sSurface) { diff --git a/src/plot_manager.h b/src/plot_manager.h index 3a3915a..68a9e25 100644 --- a/src/plot_manager.h +++ b/src/plot_manager.h @@ -185,7 +185,7 @@ namespace Manager { void setVariantSite(std::string &chrom, long start, std::string &chrom2, long stop); - void load_session(); + void loadSession(); int startUI(GrDirectContext* sContext, SkSurface *sSurface, int delay); @@ -217,6 +217,7 @@ namespace Manager { void highlightQname(); + void saveSession(); private: long frameId; @@ -243,6 +244,8 @@ namespace Manager { double yScaling; + uint32_t minGapSize; + // std::vector> extraPixelArrays; // one for each thread GLFWcursor* vCursor; diff --git a/src/segments.cpp b/src/segments.cpp index 40fa32d..c9166ad 100644 --- a/src/segments.cpp +++ b/src/segments.cpp @@ -238,14 +238,14 @@ namespace Segs { constexpr uint32_t PP_RR_MR = 50; - constexpr std::array posFirst = {INV_F, u, u, u, u, u, u, u, + alignas(64) constexpr std::array posFirst = {INV_F, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, DUP, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, DEL, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, INV_R}; - constexpr std::array mateFirst = {INV_R, u, u, u, u, u, u, u, + alignas(64) constexpr std::array mateFirst = {INV_R, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, DEL, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, @@ -258,9 +258,6 @@ namespace Segs { bam1_t *src = self->delegate; self->pos = src->core.pos; - self->reference_end = bam_endpos(src); // reference_end - already checked for 0 length cigar and mapped - self->cov_start = (int)self->pos; - self->cov_end = (int)self->reference_end; uint32_t pos, l, cigar_l, op, k; uint32_t *cigar_p; @@ -279,13 +276,15 @@ namespace Segs { self->block_starts.reserve(cigar_l); self->block_ends.reserve(cigar_l); +// uint32_t min_gap = 1000; // todo THISSS +// uint32_t last_l = 0; for (k = 0; k < cigar_l; k++) { op = cigar_p[k] & BAM_CIGAR_MASK; l = cigar_p[k] >> BAM_CIGAR_SHIFT; switch (op) { case BAM_CMATCH: case BAM_CEQUAL: case BAM_CDIFF: - if (last_op == BAM_CINS) { + if (last_op == BAM_CINS ) { //|| (last_op == BAM_CDEL && last_l < min_gap)) { if (!self->block_ends.empty() ) { self->block_ends.back() = pos + l; } @@ -298,15 +297,19 @@ namespace Segs { case BAM_CINS: self->any_ins.push_back({pos, l}); break; - case BAM_CDEL: case BAM_CREF_SKIP: + case BAM_CDEL: pos += l; +// last_l = l; + break; + case BAM_CREF_SKIP: + op = BAM_CDEL; + pos += l; +// last_l = l; break; case BAM_CSOFT_CLIP: if (k == 0) { - self->cov_start -= (int)l; self->left_soft_clip = (int)l; } else { - self->cov_end += l; self->right_soft_clip = (int)l; } break; @@ -317,6 +320,9 @@ namespace Segs { } last_op = op; } + self->reference_end = self->block_ends.back(); + self->cov_start = (int)self->pos - self->left_soft_clip; + self->cov_end = (int)self->reference_end + self->right_soft_clip; uint32_t flag = src->core.flag; @@ -332,7 +338,9 @@ namespace Segs { if (src->core.tid != src->core.mtid) { self->orient_pattern = TRA; } else { - uint32_t info = flag & PP_RR_MR; // PP_RR_MR = proper-pair, read-reverse, mate-reverse flags + // PP_RR_MR = proper-pair, read-reverse, mate-reverse flags + // 00110010 = 0010 , 00010000 , 00100000 + uint32_t info = flag & PP_RR_MR; if (self->pos <= src->core.mpos) { self->orient_pattern = posFirst[info]; } else { @@ -343,14 +351,13 @@ namespace Segs { self->orient_pattern = Segs::Pattern::NORMAL; } - if (flag & 2048 || self->has_SA) { + if (self->has_SA || flag & 2048) { self->edge_type = 2; // "SPLIT" } else if (flag & 8) { self->edge_type = 3; // "MATE_UNMAPPED" } else { self->edge_type = 1; // "NORMAL" } - self->initialized = true; // auto stop = std::chrono::high_resolution_clock::now(); // auto duration = std::chrono::duration_cast(stop - start); } @@ -628,7 +635,7 @@ namespace Segs { case BAM_CHARD_CLIP: case BAM_CEQUAL: - // Nothing to do here, just continue in the loop + // Nothing to do here break; case BAM_CDIFF: for (uint32_t i = 0; i < l; ++i) { diff --git a/src/segments.h b/src/segments.h index 742c380..24904e7 100644 --- a/src/segments.h +++ b/src/segments.h @@ -59,13 +59,10 @@ namespace Segs { bam1_t *delegate; int cov_start, cov_end, orient_pattern, left_soft_clip, right_soft_clip, y, edge_type; uint32_t pos, reference_end; - bool has_SA, initialized; + bool has_SA; //, initialized; std::vector block_starts, block_ends; std::vector any_ins; - Align(bam1_t *src) { - delegate = src; - initialized = false; - } + Align(bam1_t *src) { delegate = src; } }; struct EXPORT Mismatches { diff --git a/src/themes.cpp b/src/themes.cpp index 31df257..d8cbb52 100644 --- a/src/themes.cpp +++ b/src/themes.cpp @@ -530,6 +530,7 @@ namespace Themes { } else { theme = Themes::IgvTheme(); } + theme.name = theme_str; } if (sub.has("dimensions")) { dimensions_str = sub["dimensions"]; @@ -677,9 +678,9 @@ namespace Themes { void IniOptions::saveCurrentSession(std::string& genome_path, std::vector& bam_paths, std::vector& track_paths, std::vector& regions, - std::vector& variant_tracks_paths, + std::vector>& variant_paths_info, std::vector& commands, std::string output_session, - int mode) { + int mode, int window_x_pos, int window_y_pos) { if (output_session.empty()) { @@ -707,18 +708,25 @@ namespace Themes { count += 1; } count = 0; + for (auto& item: variant_paths_info) { + std::filesystem::path fspath(item.first); + std::string path = std::filesystem::absolute(fspath).string(); + seshIni["data"]["var" + std::to_string(count)] = path; + count += 1; + } + count = 0; for (auto& item: regions) { - seshIni["data"]["region" + std::to_string(count)] = item.toString(); + seshIni["show"]["region" + std::to_string(count)] = item.toString(); count += 1; } + seshIni["show"]["mode"] = (mode == 1) ? "tiled" : "single"; count = 0; - for (auto& item: variant_tracks_paths) { - std::filesystem::path fspath(item); - std::string path = std::filesystem::absolute(fspath).string(); - seshIni["data"]["var" + std::to_string(count)] = path; + for (auto& item: variant_paths_info) { + seshIni["show"]["var" + std::to_string(count)] = std::to_string(item.second); count += 1; } - seshIni["data"]["mode"] = (mode == 1) ? "tiled" : "single"; + seshIni["show"]["window_position"] = std::to_string(window_x_pos) + "," + std::to_string(window_y_pos); + count = 0; size_t last_refresh = 0; std::vector keep = {"egdes", "expand-tracks", "soft-clips", "sc", "mm", "mismatches", "ins", "insertions"}; diff --git a/src/themes.h b/src/themes.h index 8ee721f..ff36764 100644 --- a/src/themes.h +++ b/src/themes.h @@ -177,8 +177,9 @@ namespace Themes { void setTheme(std::string &theme_str); void saveCurrentSession(std::string& genome_path, std::vector& bam_paths, std::vector& track_paths, std::vector& regions, - std::vector& variant_tracks_paths, - std::vector& commands, std::string output_session, int mode); + std::vector>& variant_paths_info, + std::vector& commands, std::string output_session, + int mode, int window_x_pos, int window_y_pos); }; diff --git a/src/utils.cpp b/src/utils.cpp index 2939931..298c780 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -378,15 +378,21 @@ namespace Utils { int start = 0; int end = (int)s.find('x'); if (end == (int)s.size()) { - throw std::runtime_error("Error: 'x' not in dimensions"); + end = (int)s.find(','); + if (end == (int)s.size()) { + throw std::runtime_error("Error: 'x' or ',' not in dimensions"); + } } d.x = std::stoi(s.substr(start, end - start)); start = end + 1; end = (int)s.find('x', start); - d.y = std::stoi(s.substr(start, end - start)); - if (d.x == 0) { - throw std::runtime_error("Error: dimension x was 0"); + if (end == (int)s.size()) { + end = (int)s.find(','); } + d.y = std::stoi(s.substr(start, end - start)); +// if (d.x == 0) { +// throw std::runtime_error("Error: dimension x was 0"); +// } return d; } diff --git a/src/utils.h b/src/utils.h index a2d8a7c..2b63155 100644 --- a/src/utils.h +++ b/src/utils.h @@ -76,6 +76,7 @@ namespace Utils { int start, end; int markerPos, markerPosEnd; const char *refSeq; + std::vector refSeq_nibbled; std::vector> featuresInView; // one vector for each Track std::vector featureLevels; Region() { From d18c26ccb7e2187e28e1868566c816e5d4d7a5c6 Mon Sep 17 00:00:00 2001 From: kcleal Date: Fri, 7 Jun 2024 12:20:10 +0100 Subject: [PATCH 36/51] Fixed performance regression for plotting png images --- src/main.cpp | 11 +++++------ src/plot_manager.cpp | 9 +++++++-- src/plot_manager.h | 4 +++- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 3938cf6..9b275ce 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -723,16 +723,15 @@ int main(int argc, char *argv[]) { if (!regions.empty()) { // plot target regions plotter.setRasterSize(iopts.dimensions.x, iopts.dimensions.y); plotter.gap = 0; - plotter.makeRasterSurface(); -// sk_sp rasterSurface = SkSurface::MakeRasterN32Premul(iopts.dimensions.x, -// iopts.dimensions.y); -// SkCanvas *canvas = rasterSurface->getCanvas(); + sk_sp rasterSurface = SkSurface::MakeRasterN32Premul(iopts.dimensions.x, + iopts.dimensions.y); + SkCanvas *canvas = rasterSurface->getCanvas(); if (iopts.link_op == 0) { - plotter.runDrawNoBuffer(); + plotter.runDrawNoBufferOnCanvas(canvas); } else { plotter.runDraw(); } - img = plotter.rasterSurface->makeImageSnapshot(); + img = rasterSurface->makeImageSnapshot(); if (outdir.empty()) { std::string fpath; diff --git a/src/plot_manager.cpp b/src/plot_manager.cpp index 3310dd3..cfc9707 100644 --- a/src/plot_manager.cpp +++ b/src/plot_manager.cpp @@ -1301,14 +1301,14 @@ namespace Manager { runDrawOnCanvas(rasterCanvas); } - void GwPlot::runDrawNoBuffer() { + void GwPlot::runDrawNoBufferOnCanvas(SkCanvas* canvas) { // std::chrono::high_resolution_clock::time_point initial = std::chrono::high_resolution_clock::now(); if (bams.empty()) { return; } - SkCanvas* canvas = rasterCanvas; +// SkCanvas* canvas = rasterCanvas; canvas->drawPaint(opts.theme.bgPaint); fetchRefSeqs(); @@ -1371,6 +1371,11 @@ namespace Manager { // std::cerr << " time runDrawNoBuffer " << (std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - initial).count()) << std::endl; } + void GwPlot::runDrawNoBuffer() { + runDrawNoBufferOnCanvas(rasterCanvas); + } + + sk_sp GwPlot::makeImage() { makeRasterSurface(); runDraw(); diff --git a/src/plot_manager.h b/src/plot_manager.h index 68a9e25..1e3593b 100644 --- a/src/plot_manager.h +++ b/src/plot_manager.h @@ -205,7 +205,9 @@ namespace Manager { void runDrawOnCanvas(SkCanvas *canvas); - void runDrawNoBuffer(); + void runDrawNoBuffer(); // draws to canvas managed by GwPlot (slower) + + void runDrawNoBufferOnCanvas(SkCanvas* canvas); // draws to external canvas (faster) sk_sp makeImage(); From 8ec5ef2e57c5afd3b48e392f31a38cc7d202ece9 Mon Sep 17 00:00:00 2001 From: kcleal Date: Fri, 21 Jun 2024 09:51:24 +0100 Subject: [PATCH 37/51] Fixed some session file issues. Fixed frame buffer size silent bug. Started work on ideogram --- include/cytobands.h | 80 ++++++++++++++++++++++++++++++++++++++++++++ src/main.cpp | 8 +++-- src/plot_manager.cpp | 12 ++++--- src/themes.cpp | 10 +++--- src/themes.h | 2 +- 5 files changed, 97 insertions(+), 15 deletions(-) create mode 100644 include/cytobands.h diff --git a/include/cytobands.h b/include/cytobands.h new file mode 100644 index 0000000..572b220 --- /dev/null +++ b/include/cytobands.h @@ -0,0 +1,80 @@ +// +// Created by Kez Cleal on 20/06/2024. +// + +#pragma once + +#include +#include +#include +#include +#include +#include + + +struct Band { + int start, end, alpha, red, green, blue; + std::string name; +}; + + +void printIdeogram(std::vector& bands) { + std::cout << "{\n"; + for (const auto& b : bands) { + std::cout << + "{" << b.start << ", " + << b.end << ", " + << b.alpha << ", " + << b.red << ", " + << b.green << ", " + << b.blue << ", " + << b.name << "}\n" + } + std::cout << "};\n\n"; +} + + +#define next std::getline(iss, token, '\t') + +void readCytoBandFile(std::string file_path, std::vector& ideogram) { + std::ifstream band_file(file_path); + if (!band_file) { + throw std::runtime_error("Failed to open input files"); + } + std::string line, token, name, property; + while (std::getline(band_file, line)) { + std::istringstream iss(line); + next; next; + int start = std::stoi(token); + next; + int end = std::stoi(token); + next; + name = token; + next; + property = name; + + if (property == "gneg") { + ideogram.emplace_back() = {start, end, 0, 0, 0, 0, name}; + } else if (property == "gpos25") { + ideogram.emplace_back() = {start, end, 0.5, 235, 235, 235, name}; + } else if (property == "gpos50") { + ideogram.emplace_back() = {start, end, 0.5, 185, 185, 185, name}; + } else if (property == "gpos75") { + ideogram.emplace_back() = {start, end, 0.5, 110, 110, 110, name}; + } else if (property == "gpos100") { + ideogram.emplace_back() = {start, end, 0.5, 60, 60, 60, name}; + } else if (property == "acen") { + ideogram.emplace_back() = {start, end, 0.5, 220, 10, 10, name}; + } else if (property == "gvar") { + ideogram.emplace_back() = {start, end, 0.5, 10, 10, 220, name}; + } else { // try custom color scheme + try { + std::string a = Utils::split(property, ','); + ideogram.emplace_back() = {start, end, std::stoi(a[0]), std::stoi(a[1]), std::stoi(a[2]), std::stoi(a[3]), name}; + } except { + }; + + } + } + printIdeogram(ideogram); +} \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 9b275ce..b1d02ae 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -77,6 +77,7 @@ int main(int argc, char *argv[]) { } bool have_session_file = !iopts.session_file.empty(); + bool use_session = false; static const std::vector img_fmt = { "png", "pdf", "svg" }; static const std::vector img_themes = { "igv", "dark", "slate" }; @@ -307,10 +308,11 @@ int main(int argc, char *argv[]) { std::cerr << "No genomes listed, finishing\n"; std::exit(0); } + user_prompt: + if (have_session_file) { std::cout << "\nPress ENTER to load previous session or input a genome number: " << std::flush; - } else { std::cout << "\nEnter genome number: " << std::flush; } @@ -323,7 +325,7 @@ int main(int argc, char *argv[]) { } if (user_input.empty()) { if (have_session_file) { -// load_from_session(bam_paths, tracks, regions, iopts, genome); + use_session = true; } else { goto user_prompt; } @@ -499,7 +501,7 @@ int main(int argc, char *argv[]) { } if (!iopts.no_show) { // plot something to screen - if (have_session_file) { + if (use_session) { mINI::INIFile file(iopts.session_file); file.read(iopts.seshIni); if (!iopts.seshIni.has("data") || !iopts.seshIni.has("show")) { diff --git a/src/plot_manager.cpp b/src/plot_manager.cpp index cfc9707..6d5574a 100644 --- a/src/plot_manager.cpp +++ b/src/plot_manager.cpp @@ -491,7 +491,6 @@ namespace Manager { hts_idx_t* idx = sam_index_load(f, fn.c_str()); indexes.push_back(idx); } - if (opts.seshIni.has("commands")) { for (auto &item: opts.seshIni["commands"]) { inputText = item.second; @@ -518,11 +517,13 @@ namespace Manager { } int xpos, ypos; glfwGetWindowPos(window, &xpos, &ypos); - opts.saveCurrentSession(reference, bam_paths, track_paths, regions, variant_paths_info, commandHistory, "", mode, xpos, ypos); + opts.saveCurrentSession(reference, bam_paths, track_paths, regions, variant_paths_info, commandHistory, + "", mode, xpos, ypos, monitorScale); } 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) { @@ -585,8 +586,8 @@ namespace Manager { setScaling(); bboxes = Utils::imageBoundingBoxes(opts.number, (float)fb_width, (float)fb_height); - opts.dimensions.x = fb_width; - opts.dimensions.y = fb_height; +// opts.dimensions.x = fb_width; +// opts.dimensions.y = fb_height; resizeTriggered = false; sContext->abandonContext(); @@ -615,7 +616,8 @@ namespace Manager { std::exit(-1); } - SkImageInfo info = SkImageInfo::MakeN32Premul(opts.dimensions.x * monitorScale, opts.dimensions.y * monitorScale); +// SkImageInfo info = SkImageInfo::MakeN32Premul(opts.dimensions.x * monitorScale, opts.dimensions.y * monitorScale); + SkImageInfo info = SkImageInfo::MakeN32Premul(fb_width, fb_height); size_t rowBytes = info.minRowBytes(); size_t size = info.computeByteSize(rowBytes); pixelMemory.resize(size); diff --git a/src/themes.cpp b/src/themes.cpp index d8cbb52..0c7b3f8 100644 --- a/src/themes.cpp +++ b/src/themes.cpp @@ -680,9 +680,7 @@ namespace Themes { std::vector& track_paths, std::vector& regions, std::vector>& variant_paths_info, std::vector& commands, std::string output_session, - int mode, int window_x_pos, int window_y_pos) { - - + int mode, int window_x_pos, int window_y_pos, float monitorScale) { if (output_session.empty()) { if (session_file.empty()) { // fill new session std::filesystem::path gwini(ini_path); @@ -725,8 +723,7 @@ namespace Themes { seshIni["show"]["var" + std::to_string(count)] = std::to_string(item.second); count += 1; } - seshIni["show"]["window_position"] = std::to_string(window_x_pos) + "," + std::to_string(window_y_pos); - + seshIni["show"]["window_position"] = std::to_string(window_x_pos) + "x" + std::to_string(window_y_pos); count = 0; size_t last_refresh = 0; std::vector keep = {"egdes", "expand-tracks", "soft-clips", "sc", "mm", "mismatches", "ins", "insertions"}; @@ -754,7 +751,8 @@ namespace Themes { mINI::INIMap& sub = seshIni["general"]; sub["theme"] = theme_str; sub["dimensions"] = std::to_string(dimensions.x) + "x" + std::to_string(dimensions.y); - sub["indel_length"] = std::to_string(indel_length); + +// sub["indel_length"] = std::to_string(indel_length); sub["ylim"] = std::to_string(ylim); sub["coverage"] = (max_coverage) ? "true" : "false"; sub["log2_cov"] = (log2_cov) ? "true" : "false"; diff --git a/src/themes.h b/src/themes.h index ff36764..557309d 100644 --- a/src/themes.h +++ b/src/themes.h @@ -179,7 +179,7 @@ namespace Themes { std::vector& track_paths, std::vector& regions, std::vector>& variant_paths_info, std::vector& commands, std::string output_session, - int mode, int window_x_pos, int window_y_pos); + int mode, int window_x_pos, int window_y_pos, float monitorScale); }; From 960dfc7e25099d9287720f6200499c11b768a55d Mon Sep 17 00:00:00 2001 From: kcleal Date: Fri, 21 Jun 2024 11:36:30 +0100 Subject: [PATCH 38/51] Added ideogram test --- test/hg19.cytoBand.bed | 931 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 931 insertions(+) create mode 100644 test/hg19.cytoBand.bed diff --git a/test/hg19.cytoBand.bed b/test/hg19.cytoBand.bed new file mode 100644 index 0000000..9e5870a --- /dev/null +++ b/test/hg19.cytoBand.bed @@ -0,0 +1,931 @@ +chr1 0 2300000 p36.33 gneg +chr1 2300000 5400000 p36.32 gpos25 +chr1 5400000 7200000 p36.31 gneg +chr1 7200000 9200000 p36.23 gpos25 +chr1 9200000 12700000 p36.22 gneg +chr1 12700000 16200000 p36.21 gpos50 +chr1 16200000 20400000 p36.13 gneg +chr1 20400000 23900000 p36.12 gpos25 +chr1 23900000 28000000 p36.11 gneg +chr1 28000000 30200000 p35.3 gpos25 +chr1 30200000 32400000 p35.2 gneg +chr1 32400000 34600000 p35.1 gpos25 +chr1 34600000 40100000 p34.3 gneg +chr1 40100000 44100000 p34.2 gpos25 +chr1 44100000 46800000 p34.1 gneg +chr1 46800000 50700000 p33 gpos75 +chr1 50700000 56100000 p32.3 gneg +chr1 56100000 59000000 p32.2 gpos50 +chr1 59000000 61300000 p32.1 gneg +chr1 61300000 68900000 p31.3 gpos50 +chr1 68900000 69700000 p31.2 gneg +chr1 69700000 84900000 p31.1 gpos100 +chr1 84900000 88400000 p22.3 gneg +chr1 88400000 92000000 p22.2 gpos75 +chr1 92000000 94700000 p22.1 gneg +chr1 94700000 99700000 p21.3 gpos75 +chr1 99700000 102200000 p21.2 gneg +chr1 102200000 107200000 p21.1 gpos100 +chr1 107200000 111800000 p13.3 gneg +chr1 111800000 116100000 p13.2 gpos50 +chr1 116100000 117800000 p13.1 gneg +chr1 117800000 120600000 p12 gpos50 +chr1 120600000 121500000 p11.2 gneg +chr1 121500000 125000000 p11.1 acen +chr1 125000000 128900000 q11 acen +chr1 128900000 142600000 q12 gvar +chr1 142600000 147000000 q21.1 gneg +chr1 147000000 150300000 q21.2 gpos50 +chr1 150300000 155000000 q21.3 gneg +chr1 155000000 156500000 q22 gpos50 +chr1 156500000 159100000 q23.1 gneg +chr1 159100000 160500000 q23.2 gpos50 +chr1 160500000 165500000 q23.3 gneg +chr1 165500000 167200000 q24.1 gpos50 +chr1 167200000 170900000 q24.2 gneg +chr1 170900000 172900000 q24.3 gpos75 +chr1 172900000 176000000 q25.1 gneg +chr1 176000000 180300000 q25.2 gpos50 +chr1 180300000 185800000 q25.3 gneg +chr1 185800000 190800000 q31.1 gpos100 +chr1 190800000 193800000 q31.2 gneg +chr1 193800000 198700000 q31.3 gpos100 +chr1 198700000 207200000 q32.1 gneg +chr1 207200000 211500000 q32.2 gpos25 +chr1 211500000 214500000 q32.3 gneg +chr1 214500000 224100000 q41 gpos100 +chr1 224100000 224600000 q42.11 gneg +chr1 224600000 227000000 q42.12 gpos25 +chr1 227000000 230700000 q42.13 gneg +chr1 230700000 234700000 q42.2 gpos50 +chr1 234700000 236600000 q42.3 gneg +chr1 236600000 243700000 q43 gpos75 +chr1 243700000 249250621 q44 gneg +chr2 0 4400000 p25.3 gneg +chr2 4400000 7100000 p25.2 gpos50 +chr2 7100000 12200000 p25.1 gneg +chr2 12200000 16700000 p24.3 gpos75 +chr2 16700000 19200000 p24.2 gneg +chr2 19200000 24000000 p24.1 gpos75 +chr2 24000000 27900000 p23.3 gneg +chr2 27900000 30000000 p23.2 gpos25 +chr2 30000000 32100000 p23.1 gneg +chr2 32100000 36600000 p22.3 gpos75 +chr2 36600000 38600000 p22.2 gneg +chr2 38600000 41800000 p22.1 gpos50 +chr2 41800000 47800000 p21 gneg +chr2 47800000 52900000 p16.3 gpos100 +chr2 52900000 55000000 p16.2 gneg +chr2 55000000 61300000 p16.1 gpos100 +chr2 61300000 64100000 p15 gneg +chr2 64100000 68600000 p14 gpos50 +chr2 68600000 71500000 p13.3 gneg +chr2 71500000 73500000 p13.2 gpos50 +chr2 73500000 75000000 p13.1 gneg +chr2 75000000 83300000 p12 gpos100 +chr2 83300000 90500000 p11.2 gneg +chr2 90500000 93300000 p11.1 acen +chr2 93300000 96800000 q11.1 acen +chr2 96800000 102700000 q11.2 gneg +chr2 102700000 106000000 q12.1 gpos50 +chr2 106000000 107500000 q12.2 gneg +chr2 107500000 110200000 q12.3 gpos25 +chr2 110200000 114400000 q13 gneg +chr2 114400000 118800000 q14.1 gpos50 +chr2 118800000 122400000 q14.2 gneg +chr2 122400000 129900000 q14.3 gpos50 +chr2 129900000 132500000 q21.1 gneg +chr2 132500000 135100000 q21.2 gpos25 +chr2 135100000 136800000 q21.3 gneg +chr2 136800000 142200000 q22.1 gpos100 +chr2 142200000 144100000 q22.2 gneg +chr2 144100000 148700000 q22.3 gpos100 +chr2 148700000 149900000 q23.1 gneg +chr2 149900000 150500000 q23.2 gpos25 +chr2 150500000 154900000 q23.3 gneg +chr2 154900000 159800000 q24.1 gpos75 +chr2 159800000 163700000 q24.2 gneg +chr2 163700000 169700000 q24.3 gpos75 +chr2 169700000 178000000 q31.1 gneg +chr2 178000000 180600000 q31.2 gpos50 +chr2 180600000 183000000 q31.3 gneg +chr2 183000000 189400000 q32.1 gpos75 +chr2 189400000 191900000 q32.2 gneg +chr2 191900000 197400000 q32.3 gpos75 +chr2 197400000 203300000 q33.1 gneg +chr2 203300000 204900000 q33.2 gpos50 +chr2 204900000 209000000 q33.3 gneg +chr2 209000000 215300000 q34 gpos100 +chr2 215300000 221500000 q35 gneg +chr2 221500000 225200000 q36.1 gpos75 +chr2 225200000 226100000 q36.2 gneg +chr2 226100000 231000000 q36.3 gpos100 +chr2 231000000 235600000 q37.1 gneg +chr2 235600000 237300000 q37.2 gpos50 +chr2 237300000 243199373 q37.3 gneg +chr3 0 2800000 p26.3 gpos50 +chr3 2800000 4000000 p26.2 gneg +chr3 4000000 8700000 p26.1 gpos50 +chr3 8700000 11800000 p25.3 gneg +chr3 11800000 13300000 p25.2 gpos25 +chr3 13300000 16400000 p25.1 gneg +chr3 16400000 23900000 p24.3 gpos100 +chr3 23900000 26400000 p24.2 gneg +chr3 26400000 30900000 p24.1 gpos75 +chr3 30900000 32100000 p23 gneg +chr3 32100000 36500000 p22.3 gpos50 +chr3 36500000 39400000 p22.2 gneg +chr3 39400000 43700000 p22.1 gpos75 +chr3 43700000 44100000 p21.33 gneg +chr3 44100000 44200000 p21.32 gpos50 +chr3 44200000 50600000 p21.31 gneg +chr3 50600000 52300000 p21.2 gpos25 +chr3 52300000 54400000 p21.1 gneg +chr3 54400000 58600000 p14.3 gpos50 +chr3 58600000 63700000 p14.2 gneg +chr3 63700000 69800000 p14.1 gpos50 +chr3 69800000 74200000 p13 gneg +chr3 74200000 79800000 p12.3 gpos75 +chr3 79800000 83500000 p12.2 gneg +chr3 83500000 87200000 p12.1 gpos75 +chr3 87200000 87900000 p11.2 gneg +chr3 87900000 91000000 p11.1 acen +chr3 91000000 93900000 q11.1 acen +chr3 93900000 98300000 q11.2 gvar +chr3 98300000 100000000 q12.1 gneg +chr3 100000000 100900000 q12.2 gpos25 +chr3 100900000 102800000 q12.3 gneg +chr3 102800000 106200000 q13.11 gpos75 +chr3 106200000 107900000 q13.12 gneg +chr3 107900000 111300000 q13.13 gpos50 +chr3 111300000 113500000 q13.2 gneg +chr3 113500000 117300000 q13.31 gpos75 +chr3 117300000 119000000 q13.32 gneg +chr3 119000000 121900000 q13.33 gpos75 +chr3 121900000 123800000 q21.1 gneg +chr3 123800000 125800000 q21.2 gpos25 +chr3 125800000 129200000 q21.3 gneg +chr3 129200000 133700000 q22.1 gpos25 +chr3 133700000 135700000 q22.2 gneg +chr3 135700000 138700000 q22.3 gpos25 +chr3 138700000 142800000 q23 gneg +chr3 142800000 148900000 q24 gpos100 +chr3 148900000 152100000 q25.1 gneg +chr3 152100000 155000000 q25.2 gpos50 +chr3 155000000 157000000 q25.31 gneg +chr3 157000000 159000000 q25.32 gpos50 +chr3 159000000 160700000 q25.33 gneg +chr3 160700000 167600000 q26.1 gpos100 +chr3 167600000 170900000 q26.2 gneg +chr3 170900000 175700000 q26.31 gpos75 +chr3 175700000 179000000 q26.32 gneg +chr3 179000000 182700000 q26.33 gpos75 +chr3 182700000 184500000 q27.1 gneg +chr3 184500000 186000000 q27.2 gpos25 +chr3 186000000 187900000 q27.3 gneg +chr3 187900000 192300000 q28 gpos75 +chr3 192300000 198022430 q29 gneg +chr4 0 4500000 p16.3 gneg +chr4 4500000 6000000 p16.2 gpos25 +chr4 6000000 11300000 p16.1 gneg +chr4 11300000 15200000 p15.33 gpos50 +chr4 15200000 17800000 p15.32 gneg +chr4 17800000 21300000 p15.31 gpos75 +chr4 21300000 27700000 p15.2 gneg +chr4 27700000 35800000 p15.1 gpos100 +chr4 35800000 41200000 p14 gneg +chr4 41200000 44600000 p13 gpos50 +chr4 44600000 48200000 p12 gneg +chr4 48200000 50400000 p11 acen +chr4 50400000 52700000 q11 acen +chr4 52700000 59500000 q12 gneg +chr4 59500000 66600000 q13.1 gpos100 +chr4 66600000 70500000 q13.2 gneg +chr4 70500000 76300000 q13.3 gpos75 +chr4 76300000 78900000 q21.1 gneg +chr4 78900000 82400000 q21.21 gpos50 +chr4 82400000 84100000 q21.22 gneg +chr4 84100000 86900000 q21.23 gpos25 +chr4 86900000 88000000 q21.3 gneg +chr4 88000000 93700000 q22.1 gpos75 +chr4 93700000 95100000 q22.2 gneg +chr4 95100000 98800000 q22.3 gpos75 +chr4 98800000 101100000 q23 gneg +chr4 101100000 107700000 q24 gpos50 +chr4 107700000 114100000 q25 gneg +chr4 114100000 120800000 q26 gpos75 +chr4 120800000 123800000 q27 gneg +chr4 123800000 128800000 q28.1 gpos50 +chr4 128800000 131100000 q28.2 gneg +chr4 131100000 139500000 q28.3 gpos100 +chr4 139500000 141500000 q31.1 gneg +chr4 141500000 146800000 q31.21 gpos25 +chr4 146800000 148500000 q31.22 gneg +chr4 148500000 151100000 q31.23 gpos25 +chr4 151100000 155600000 q31.3 gneg +chr4 155600000 161800000 q32.1 gpos100 +chr4 161800000 164500000 q32.2 gneg +chr4 164500000 170100000 q32.3 gpos100 +chr4 170100000 171900000 q33 gneg +chr4 171900000 176300000 q34.1 gpos75 +chr4 176300000 177500000 q34.2 gneg +chr4 177500000 183200000 q34.3 gpos100 +chr4 183200000 187100000 q35.1 gneg +chr4 187100000 191154276 q35.2 gpos25 +chr5 0 4500000 p15.33 gneg +chr5 4500000 6300000 p15.32 gpos25 +chr5 6300000 9800000 p15.31 gneg +chr5 9800000 15000000 p15.2 gpos50 +chr5 15000000 18400000 p15.1 gneg +chr5 18400000 23300000 p14.3 gpos100 +chr5 23300000 24600000 p14.2 gneg +chr5 24600000 28900000 p14.1 gpos100 +chr5 28900000 33800000 p13.3 gneg +chr5 33800000 38400000 p13.2 gpos25 +chr5 38400000 42500000 p13.1 gneg +chr5 42500000 46100000 p12 gpos50 +chr5 46100000 48400000 p11 acen +chr5 48400000 50700000 q11.1 acen +chr5 50700000 58900000 q11.2 gneg +chr5 58900000 62900000 q12.1 gpos75 +chr5 62900000 63200000 q12.2 gneg +chr5 63200000 66700000 q12.3 gpos75 +chr5 66700000 68400000 q13.1 gneg +chr5 68400000 73300000 q13.2 gpos50 +chr5 73300000 76900000 q13.3 gneg +chr5 76900000 81400000 q14.1 gpos50 +chr5 81400000 82800000 q14.2 gneg +chr5 82800000 92300000 q14.3 gpos100 +chr5 92300000 98200000 q15 gneg +chr5 98200000 102800000 q21.1 gpos100 +chr5 102800000 104500000 q21.2 gneg +chr5 104500000 109600000 q21.3 gpos100 +chr5 109600000 111500000 q22.1 gneg +chr5 111500000 113100000 q22.2 gpos50 +chr5 113100000 115200000 q22.3 gneg +chr5 115200000 121400000 q23.1 gpos100 +chr5 121400000 127300000 q23.2 gneg +chr5 127300000 130600000 q23.3 gpos100 +chr5 130600000 136200000 q31.1 gneg +chr5 136200000 139500000 q31.2 gpos25 +chr5 139500000 144500000 q31.3 gneg +chr5 144500000 149800000 q32 gpos75 +chr5 149800000 152700000 q33.1 gneg +chr5 152700000 155700000 q33.2 gpos50 +chr5 155700000 159900000 q33.3 gneg +chr5 159900000 168500000 q34 gpos100 +chr5 168500000 172800000 q35.1 gneg +chr5 172800000 176600000 q35.2 gpos25 +chr5 176600000 180915260 q35.3 gneg +chr6 0 2300000 p25.3 gneg +chr6 2300000 4200000 p25.2 gpos25 +chr6 4200000 7100000 p25.1 gneg +chr6 7100000 10600000 p24.3 gpos50 +chr6 10600000 11600000 p24.2 gneg +chr6 11600000 13400000 p24.1 gpos25 +chr6 13400000 15200000 p23 gneg +chr6 15200000 25200000 p22.3 gpos75 +chr6 25200000 27000000 p22.2 gneg +chr6 27000000 30400000 p22.1 gpos50 +chr6 30400000 32100000 p21.33 gneg +chr6 32100000 33500000 p21.32 gpos25 +chr6 33500000 36600000 p21.31 gneg +chr6 36600000 40500000 p21.2 gpos25 +chr6 40500000 46200000 p21.1 gneg +chr6 46200000 51800000 p12.3 gpos100 +chr6 51800000 52900000 p12.2 gneg +chr6 52900000 57000000 p12.1 gpos100 +chr6 57000000 58700000 p11.2 gneg +chr6 58700000 61000000 p11.1 acen +chr6 61000000 63300000 q11.1 acen +chr6 63300000 63400000 q11.2 gneg +chr6 63400000 70000000 q12 gpos100 +chr6 70000000 75900000 q13 gneg +chr6 75900000 83900000 q14.1 gpos50 +chr6 83900000 84900000 q14.2 gneg +chr6 84900000 88000000 q14.3 gpos50 +chr6 88000000 93100000 q15 gneg +chr6 93100000 99500000 q16.1 gpos100 +chr6 99500000 100600000 q16.2 gneg +chr6 100600000 105500000 q16.3 gpos100 +chr6 105500000 114600000 q21 gneg +chr6 114600000 118300000 q22.1 gpos75 +chr6 118300000 118500000 q22.2 gneg +chr6 118500000 126100000 q22.31 gpos100 +chr6 126100000 127100000 q22.32 gneg +chr6 127100000 130300000 q22.33 gpos75 +chr6 130300000 131200000 q23.1 gneg +chr6 131200000 135200000 q23.2 gpos50 +chr6 135200000 139000000 q23.3 gneg +chr6 139000000 142800000 q24.1 gpos75 +chr6 142800000 145600000 q24.2 gneg +chr6 145600000 149000000 q24.3 gpos75 +chr6 149000000 152500000 q25.1 gneg +chr6 152500000 155500000 q25.2 gpos50 +chr6 155500000 161000000 q25.3 gneg +chr6 161000000 164500000 q26 gpos50 +chr6 164500000 171115067 q27 gneg +chr7 0 2800000 p22.3 gneg +chr7 2800000 4500000 p22.2 gpos25 +chr7 4500000 7300000 p22.1 gneg +chr7 7300000 13800000 p21.3 gpos100 +chr7 13800000 16500000 p21.2 gneg +chr7 16500000 20900000 p21.1 gpos100 +chr7 20900000 25500000 p15.3 gneg +chr7 25500000 28000000 p15.2 gpos50 +chr7 28000000 28800000 p15.1 gneg +chr7 28800000 35000000 p14.3 gpos75 +chr7 35000000 37200000 p14.2 gneg +chr7 37200000 43300000 p14.1 gpos75 +chr7 43300000 45400000 p13 gneg +chr7 45400000 49000000 p12.3 gpos75 +chr7 49000000 50500000 p12.2 gneg +chr7 50500000 54000000 p12.1 gpos75 +chr7 54000000 58000000 p11.2 gneg +chr7 58000000 59900000 p11.1 acen +chr7 59900000 61700000 q11.1 acen +chr7 61700000 67000000 q11.21 gneg +chr7 67000000 72200000 q11.22 gpos50 +chr7 72200000 77500000 q11.23 gneg +chr7 77500000 86400000 q21.11 gpos100 +chr7 86400000 88200000 q21.12 gneg +chr7 88200000 91100000 q21.13 gpos75 +chr7 91100000 92800000 q21.2 gneg +chr7 92800000 98000000 q21.3 gpos75 +chr7 98000000 103800000 q22.1 gneg +chr7 103800000 104500000 q22.2 gpos50 +chr7 104500000 107400000 q22.3 gneg +chr7 107400000 114600000 q31.1 gpos75 +chr7 114600000 117400000 q31.2 gneg +chr7 117400000 121100000 q31.31 gpos75 +chr7 121100000 123800000 q31.32 gneg +chr7 123800000 127100000 q31.33 gpos75 +chr7 127100000 129200000 q32.1 gneg +chr7 129200000 130400000 q32.2 gpos25 +chr7 130400000 132600000 q32.3 gneg +chr7 132600000 138200000 q33 gpos50 +chr7 138200000 143100000 q34 gneg +chr7 143100000 147900000 q35 gpos75 +chr7 147900000 152600000 q36.1 gneg +chr7 152600000 155100000 q36.2 gpos25 +chr7 155100000 159138663 q36.3 gneg +chr8 0 2200000 p23.3 gneg +chr8 2200000 6200000 p23.2 gpos75 +chr8 6200000 12700000 p23.1 gneg +chr8 12700000 19000000 p22 gpos100 +chr8 19000000 23300000 p21.3 gneg +chr8 23300000 27400000 p21.2 gpos50 +chr8 27400000 28800000 p21.1 gneg +chr8 28800000 36500000 p12 gpos75 +chr8 36500000 38300000 p11.23 gneg +chr8 38300000 39700000 p11.22 gpos25 +chr8 39700000 43100000 p11.21 gneg +chr8 43100000 45600000 p11.1 acen +chr8 45600000 48100000 q11.1 acen +chr8 48100000 52200000 q11.21 gneg +chr8 52200000 52600000 q11.22 gpos75 +chr8 52600000 55500000 q11.23 gneg +chr8 55500000 61600000 q12.1 gpos50 +chr8 61600000 62200000 q12.2 gneg +chr8 62200000 66000000 q12.3 gpos50 +chr8 66000000 68000000 q13.1 gneg +chr8 68000000 70500000 q13.2 gpos50 +chr8 70500000 73900000 q13.3 gneg +chr8 73900000 78300000 q21.11 gpos100 +chr8 78300000 80100000 q21.12 gneg +chr8 80100000 84600000 q21.13 gpos75 +chr8 84600000 86900000 q21.2 gneg +chr8 86900000 93300000 q21.3 gpos100 +chr8 93300000 99000000 q22.1 gneg +chr8 99000000 101600000 q22.2 gpos25 +chr8 101600000 106200000 q22.3 gneg +chr8 106200000 110500000 q23.1 gpos75 +chr8 110500000 112100000 q23.2 gneg +chr8 112100000 117700000 q23.3 gpos100 +chr8 117700000 119200000 q24.11 gneg +chr8 119200000 122500000 q24.12 gpos50 +chr8 122500000 127300000 q24.13 gneg +chr8 127300000 131500000 q24.21 gpos50 +chr8 131500000 136400000 q24.22 gneg +chr8 136400000 139900000 q24.23 gpos75 +chr8 139900000 146364022 q24.3 gneg +chr9 0 2200000 p24.3 gneg +chr9 2200000 4600000 p24.2 gpos25 +chr9 4600000 9000000 p24.1 gneg +chr9 9000000 14200000 p23 gpos75 +chr9 14200000 16600000 p22.3 gneg +chr9 16600000 18500000 p22.2 gpos25 +chr9 18500000 19900000 p22.1 gneg +chr9 19900000 25600000 p21.3 gpos100 +chr9 25600000 28000000 p21.2 gneg +chr9 28000000 33200000 p21.1 gpos100 +chr9 33200000 36300000 p13.3 gneg +chr9 36300000 38400000 p13.2 gpos25 +chr9 38400000 41000000 p13.1 gneg +chr9 41000000 43600000 p12 gpos50 +chr9 43600000 47300000 p11.2 gneg +chr9 47300000 49000000 p11.1 acen +chr9 49000000 50700000 q11 acen +chr9 50700000 65900000 q12 gvar +chr9 65900000 68700000 q13 gneg +chr9 68700000 72200000 q21.11 gpos25 +chr9 72200000 74000000 q21.12 gneg +chr9 74000000 79200000 q21.13 gpos50 +chr9 79200000 81100000 q21.2 gneg +chr9 81100000 84100000 q21.31 gpos50 +chr9 84100000 86900000 q21.32 gneg +chr9 86900000 90400000 q21.33 gpos50 +chr9 90400000 91800000 q22.1 gneg +chr9 91800000 93900000 q22.2 gpos25 +chr9 93900000 96600000 q22.31 gneg +chr9 96600000 99300000 q22.32 gpos25 +chr9 99300000 102600000 q22.33 gneg +chr9 102600000 108200000 q31.1 gpos100 +chr9 108200000 111300000 q31.2 gneg +chr9 111300000 114900000 q31.3 gpos25 +chr9 114900000 117700000 q32 gneg +chr9 117700000 122500000 q33.1 gpos75 +chr9 122500000 125800000 q33.2 gneg +chr9 125800000 130300000 q33.3 gpos25 +chr9 130300000 133500000 q34.11 gneg +chr9 133500000 134000000 q34.12 gpos25 +chr9 134000000 135900000 q34.13 gneg +chr9 135900000 137400000 q34.2 gpos25 +chr9 137400000 141213431 q34.3 gneg +chr10 0 3000000 p15.3 gneg +chr10 3000000 3800000 p15.2 gpos25 +chr10 3800000 6600000 p15.1 gneg +chr10 6600000 12200000 p14 gpos75 +chr10 12200000 17300000 p13 gneg +chr10 17300000 18600000 p12.33 gpos75 +chr10 18600000 18700000 p12.32 gneg +chr10 18700000 22600000 p12.31 gpos75 +chr10 22600000 24600000 p12.2 gneg +chr10 24600000 29600000 p12.1 gpos50 +chr10 29600000 31300000 p11.23 gneg +chr10 31300000 34400000 p11.22 gpos25 +chr10 34400000 38000000 p11.21 gneg +chr10 38000000 40200000 p11.1 acen +chr10 40200000 42300000 q11.1 acen +chr10 42300000 46100000 q11.21 gneg +chr10 46100000 49900000 q11.22 gpos25 +chr10 49900000 52900000 q11.23 gneg +chr10 52900000 61200000 q21.1 gpos100 +chr10 61200000 64500000 q21.2 gneg +chr10 64500000 70600000 q21.3 gpos100 +chr10 70600000 74900000 q22.1 gneg +chr10 74900000 77700000 q22.2 gpos50 +chr10 77700000 82000000 q22.3 gneg +chr10 82000000 87900000 q23.1 gpos100 +chr10 87900000 89500000 q23.2 gneg +chr10 89500000 92900000 q23.31 gpos75 +chr10 92900000 94100000 q23.32 gneg +chr10 94100000 97000000 q23.33 gpos50 +chr10 97000000 99300000 q24.1 gneg +chr10 99300000 101900000 q24.2 gpos50 +chr10 101900000 103000000 q24.31 gneg +chr10 103000000 104900000 q24.32 gpos25 +chr10 104900000 105800000 q24.33 gneg +chr10 105800000 111900000 q25.1 gpos100 +chr10 111900000 114900000 q25.2 gneg +chr10 114900000 119100000 q25.3 gpos75 +chr10 119100000 121700000 q26.11 gneg +chr10 121700000 123100000 q26.12 gpos50 +chr10 123100000 127500000 q26.13 gneg +chr10 127500000 130600000 q26.2 gpos50 +chr10 130600000 135534747 q26.3 gneg +chr11 0 2800000 p15.5 gneg +chr11 2800000 10700000 p15.4 gpos50 +chr11 10700000 12700000 p15.3 gneg +chr11 12700000 16200000 p15.2 gpos50 +chr11 16200000 21700000 p15.1 gneg +chr11 21700000 26100000 p14.3 gpos100 +chr11 26100000 27200000 p14.2 gneg +chr11 27200000 31000000 p14.1 gpos75 +chr11 31000000 36400000 p13 gneg +chr11 36400000 43500000 p12 gpos100 +chr11 43500000 48800000 p11.2 gneg +chr11 48800000 51600000 p11.12 gpos75 +chr11 51600000 53700000 p11.11 acen +chr11 53700000 55700000 q11 acen +chr11 55700000 59900000 q12.1 gpos75 +chr11 59900000 61700000 q12.2 gneg +chr11 61700000 63400000 q12.3 gpos25 +chr11 63400000 65900000 q13.1 gneg +chr11 65900000 68400000 q13.2 gpos25 +chr11 68400000 70400000 q13.3 gneg +chr11 70400000 75200000 q13.4 gpos50 +chr11 75200000 77100000 q13.5 gneg +chr11 77100000 85600000 q14.1 gpos100 +chr11 85600000 88300000 q14.2 gneg +chr11 88300000 92800000 q14.3 gpos100 +chr11 92800000 97200000 q21 gneg +chr11 97200000 102100000 q22.1 gpos100 +chr11 102100000 102900000 q22.2 gneg +chr11 102900000 110400000 q22.3 gpos100 +chr11 110400000 112500000 q23.1 gneg +chr11 112500000 114500000 q23.2 gpos50 +chr11 114500000 121200000 q23.3 gneg +chr11 121200000 123900000 q24.1 gpos50 +chr11 123900000 127800000 q24.2 gneg +chr11 127800000 130800000 q24.3 gpos50 +chr11 130800000 135006516 q25 gneg +chr12 0 3300000 p13.33 gneg +chr12 3300000 5400000 p13.32 gpos25 +chr12 5400000 10100000 p13.31 gneg +chr12 10100000 12800000 p13.2 gpos75 +chr12 12800000 14800000 p13.1 gneg +chr12 14800000 20000000 p12.3 gpos100 +chr12 20000000 21300000 p12.2 gneg +chr12 21300000 26500000 p12.1 gpos100 +chr12 26500000 27800000 p11.23 gneg +chr12 27800000 30700000 p11.22 gpos50 +chr12 30700000 33300000 p11.21 gneg +chr12 33300000 35800000 p11.1 acen +chr12 35800000 38200000 q11 acen +chr12 38200000 46400000 q12 gpos100 +chr12 46400000 49100000 q13.11 gneg +chr12 49100000 51500000 q13.12 gpos25 +chr12 51500000 54900000 q13.13 gneg +chr12 54900000 56600000 q13.2 gpos25 +chr12 56600000 58100000 q13.3 gneg +chr12 58100000 63100000 q14.1 gpos75 +chr12 63100000 65100000 q14.2 gneg +chr12 65100000 67700000 q14.3 gpos50 +chr12 67700000 71500000 q15 gneg +chr12 71500000 75700000 q21.1 gpos75 +chr12 75700000 80300000 q21.2 gneg +chr12 80300000 86700000 q21.31 gpos100 +chr12 86700000 89000000 q21.32 gneg +chr12 89000000 92600000 q21.33 gpos100 +chr12 92600000 96200000 q22 gneg +chr12 96200000 101600000 q23.1 gpos75 +chr12 101600000 103800000 q23.2 gneg +chr12 103800000 109000000 q23.3 gpos50 +chr12 109000000 111700000 q24.11 gneg +chr12 111700000 112300000 q24.12 gpos25 +chr12 112300000 114300000 q24.13 gneg +chr12 114300000 116800000 q24.21 gpos50 +chr12 116800000 118100000 q24.22 gneg +chr12 118100000 120700000 q24.23 gpos50 +chr12 120700000 125900000 q24.31 gneg +chr12 125900000 129300000 q24.32 gpos50 +chr12 129300000 133851895 q24.33 gneg +chr13 0 4500000 p13 gvar +chr13 4500000 10000000 p12 stalk +chr13 10000000 16300000 p11.2 gvar +chr13 16300000 17900000 p11.1 acen +chr13 17900000 19500000 q11 acen +chr13 19500000 23300000 q12.11 gneg +chr13 23300000 25500000 q12.12 gpos25 +chr13 25500000 27800000 q12.13 gneg +chr13 27800000 28900000 q12.2 gpos25 +chr13 28900000 32200000 q12.3 gneg +chr13 32200000 34000000 q13.1 gpos50 +chr13 34000000 35500000 q13.2 gneg +chr13 35500000 40100000 q13.3 gpos75 +chr13 40100000 45200000 q14.11 gneg +chr13 45200000 45800000 q14.12 gpos25 +chr13 45800000 47300000 q14.13 gneg +chr13 47300000 50900000 q14.2 gpos50 +chr13 50900000 55300000 q14.3 gneg +chr13 55300000 59600000 q21.1 gpos100 +chr13 59600000 62300000 q21.2 gneg +chr13 62300000 65700000 q21.31 gpos75 +chr13 65700000 68600000 q21.32 gneg +chr13 68600000 73300000 q21.33 gpos100 +chr13 73300000 75400000 q22.1 gneg +chr13 75400000 77200000 q22.2 gpos50 +chr13 77200000 79000000 q22.3 gneg +chr13 79000000 87700000 q31.1 gpos100 +chr13 87700000 90000000 q31.2 gneg +chr13 90000000 95000000 q31.3 gpos100 +chr13 95000000 98200000 q32.1 gneg +chr13 98200000 99300000 q32.2 gpos25 +chr13 99300000 101700000 q32.3 gneg +chr13 101700000 104800000 q33.1 gpos100 +chr13 104800000 107000000 q33.2 gneg +chr13 107000000 110300000 q33.3 gpos100 +chr13 110300000 115169878 q34 gneg +chr14 0 3700000 p13 gvar +chr14 3700000 8100000 p12 stalk +chr14 8100000 16100000 p11.2 gvar +chr14 16100000 17600000 p11.1 acen +chr14 17600000 19100000 q11.1 acen +chr14 19100000 24600000 q11.2 gneg +chr14 24600000 33300000 q12 gpos100 +chr14 33300000 35300000 q13.1 gneg +chr14 35300000 36600000 q13.2 gpos50 +chr14 36600000 37800000 q13.3 gneg +chr14 37800000 43500000 q21.1 gpos100 +chr14 43500000 47200000 q21.2 gneg +chr14 47200000 50900000 q21.3 gpos100 +chr14 50900000 54100000 q22.1 gneg +chr14 54100000 55500000 q22.2 gpos25 +chr14 55500000 58100000 q22.3 gneg +chr14 58100000 62100000 q23.1 gpos75 +chr14 62100000 64800000 q23.2 gneg +chr14 64800000 67900000 q23.3 gpos50 +chr14 67900000 70200000 q24.1 gneg +chr14 70200000 73800000 q24.2 gpos50 +chr14 73800000 79300000 q24.3 gneg +chr14 79300000 83600000 q31.1 gpos100 +chr14 83600000 84900000 q31.2 gneg +chr14 84900000 89800000 q31.3 gpos100 +chr14 89800000 91900000 q32.11 gneg +chr14 91900000 94700000 q32.12 gpos25 +chr14 94700000 96300000 q32.13 gneg +chr14 96300000 101400000 q32.2 gpos50 +chr14 101400000 103200000 q32.31 gneg +chr14 103200000 104000000 q32.32 gpos50 +chr14 104000000 107349540 q32.33 gneg +chr15 0 3900000 p13 gvar +chr15 3900000 8700000 p12 stalk +chr15 8700000 15800000 p11.2 gvar +chr15 15800000 19000000 p11.1 acen +chr15 19000000 20700000 q11.1 acen +chr15 20700000 25700000 q11.2 gneg +chr15 25700000 28100000 q12 gpos50 +chr15 28100000 30300000 q13.1 gneg +chr15 30300000 31200000 q13.2 gpos50 +chr15 31200000 33600000 q13.3 gneg +chr15 33600000 40100000 q14 gpos75 +chr15 40100000 42800000 q15.1 gneg +chr15 42800000 43600000 q15.2 gpos25 +chr15 43600000 44800000 q15.3 gneg +chr15 44800000 49500000 q21.1 gpos75 +chr15 49500000 52900000 q21.2 gneg +chr15 52900000 59100000 q21.3 gpos75 +chr15 59100000 59300000 q22.1 gneg +chr15 59300000 63700000 q22.2 gpos25 +chr15 63700000 67200000 q22.31 gneg +chr15 67200000 67300000 q22.32 gpos25 +chr15 67300000 67500000 q22.33 gneg +chr15 67500000 72700000 q23 gpos25 +chr15 72700000 75200000 q24.1 gneg +chr15 75200000 76600000 q24.2 gpos25 +chr15 76600000 78300000 q24.3 gneg +chr15 78300000 81700000 q25.1 gpos50 +chr15 81700000 85200000 q25.2 gneg +chr15 85200000 89100000 q25.3 gpos50 +chr15 89100000 94300000 q26.1 gneg +chr15 94300000 98500000 q26.2 gpos50 +chr15 98500000 102531392 q26.3 gneg +chr16 0 7900000 p13.3 gneg +chr16 7900000 10500000 p13.2 gpos50 +chr16 10500000 12600000 p13.13 gneg +chr16 12600000 14800000 p13.12 gpos50 +chr16 14800000 16800000 p13.11 gneg +chr16 16800000 21200000 p12.3 gpos50 +chr16 21200000 24200000 p12.2 gneg +chr16 24200000 28100000 p12.1 gpos50 +chr16 28100000 34600000 p11.2 gneg +chr16 34600000 36600000 p11.1 acen +chr16 36600000 38600000 q11.1 acen +chr16 38600000 47000000 q11.2 gvar +chr16 47000000 52600000 q12.1 gneg +chr16 52600000 56700000 q12.2 gpos50 +chr16 56700000 57400000 q13 gneg +chr16 57400000 66700000 q21 gpos100 +chr16 66700000 70800000 q22.1 gneg +chr16 70800000 72900000 q22.2 gpos50 +chr16 72900000 74100000 q22.3 gneg +chr16 74100000 79200000 q23.1 gpos75 +chr16 79200000 81700000 q23.2 gneg +chr16 81700000 84200000 q23.3 gpos50 +chr16 84200000 87100000 q24.1 gneg +chr16 87100000 88700000 q24.2 gpos25 +chr16 88700000 90354753 q24.3 gneg +chr17 0 3300000 p13.3 gneg +chr17 3300000 6500000 p13.2 gpos50 +chr17 6500000 10700000 p13.1 gneg +chr17 10700000 16000000 p12 gpos75 +chr17 16000000 22200000 p11.2 gneg +chr17 22200000 24000000 p11.1 acen +chr17 24000000 25800000 q11.1 acen +chr17 25800000 31800000 q11.2 gneg +chr17 31800000 38100000 q12 gpos50 +chr17 38100000 38400000 q21.1 gneg +chr17 38400000 40900000 q21.2 gpos25 +chr17 40900000 44900000 q21.31 gneg +chr17 44900000 47400000 q21.32 gpos25 +chr17 47400000 50200000 q21.33 gneg +chr17 50200000 57600000 q22 gpos75 +chr17 57600000 58300000 q23.1 gneg +chr17 58300000 61100000 q23.2 gpos75 +chr17 61100000 62600000 q23.3 gneg +chr17 62600000 64200000 q24.1 gpos50 +chr17 64200000 67100000 q24.2 gneg +chr17 67100000 70900000 q24.3 gpos75 +chr17 70900000 74800000 q25.1 gneg +chr17 74800000 75300000 q25.2 gpos25 +chr17 75300000 81195210 q25.3 gneg +chr18 0 2900000 p11.32 gneg +chr18 2900000 7100000 p11.31 gpos50 +chr18 7100000 8500000 p11.23 gneg +chr18 8500000 10900000 p11.22 gpos25 +chr18 10900000 15400000 p11.21 gneg +chr18 15400000 17200000 p11.1 acen +chr18 17200000 19000000 q11.1 acen +chr18 19000000 25000000 q11.2 gneg +chr18 25000000 32700000 q12.1 gpos100 +chr18 32700000 37200000 q12.2 gneg +chr18 37200000 43500000 q12.3 gpos75 +chr18 43500000 48200000 q21.1 gneg +chr18 48200000 53800000 q21.2 gpos75 +chr18 53800000 56200000 q21.31 gneg +chr18 56200000 59000000 q21.32 gpos50 +chr18 59000000 61600000 q21.33 gneg +chr18 61600000 66800000 q22.1 gpos100 +chr18 66800000 68700000 q22.2 gneg +chr18 68700000 73100000 q22.3 gpos25 +chr18 73100000 78077248 q23 gneg +chr19 0 6900000 p13.3 gneg +chr19 6900000 13900000 p13.2 gpos25 +chr19 13900000 14000000 p13.13 gneg +chr19 14000000 16300000 p13.12 gpos25 +chr19 16300000 20000000 p13.11 gneg +chr19 20000000 24400000 p12 gvar +chr19 24400000 26500000 p11 acen +chr19 26500000 28600000 q11 acen +chr19 28600000 32400000 q12 gvar +chr19 32400000 35500000 q13.11 gneg +chr19 35500000 38300000 q13.12 gpos25 +chr19 38300000 38700000 q13.13 gneg +chr19 38700000 43400000 q13.2 gpos25 +chr19 43400000 45200000 q13.31 gneg +chr19 45200000 48000000 q13.32 gpos25 +chr19 48000000 51400000 q13.33 gneg +chr19 51400000 53600000 q13.41 gpos25 +chr19 53600000 56300000 q13.42 gneg +chr19 56300000 59128983 q13.43 gpos25 +chr20 0 5100000 p13 gneg +chr20 5100000 9200000 p12.3 gpos75 +chr20 9200000 12100000 p12.2 gneg +chr20 12100000 17900000 p12.1 gpos75 +chr20 17900000 21300000 p11.23 gneg +chr20 21300000 22300000 p11.22 gpos25 +chr20 22300000 25600000 p11.21 gneg +chr20 25600000 27500000 p11.1 acen +chr20 27500000 29400000 q11.1 acen +chr20 29400000 32100000 q11.21 gneg +chr20 32100000 34400000 q11.22 gpos25 +chr20 34400000 37600000 q11.23 gneg +chr20 37600000 41700000 q12 gpos75 +chr20 41700000 42100000 q13.11 gneg +chr20 42100000 46400000 q13.12 gpos25 +chr20 46400000 49800000 q13.13 gneg +chr20 49800000 55000000 q13.2 gpos75 +chr20 55000000 56500000 q13.31 gneg +chr20 56500000 58400000 q13.32 gpos50 +chr20 58400000 63025520 q13.33 gneg +chr21 0 2800000 p13 gvar +chr21 2800000 6800000 p12 stalk +chr21 6800000 10900000 p11.2 gvar +chr21 10900000 13200000 p11.1 acen +chr21 13200000 14300000 q11.1 acen +chr21 14300000 16400000 q11.2 gneg +chr21 16400000 24000000 q21.1 gpos100 +chr21 24000000 26800000 q21.2 gneg +chr21 26800000 31500000 q21.3 gpos75 +chr21 31500000 35800000 q22.11 gneg +chr21 35800000 37800000 q22.12 gpos50 +chr21 37800000 39700000 q22.13 gneg +chr21 39700000 42600000 q22.2 gpos50 +chr21 42600000 48129895 q22.3 gneg +chr22 0 3800000 p13 gvar +chr22 3800000 8300000 p12 stalk +chr22 8300000 12200000 p11.2 gvar +chr22 12200000 14700000 p11.1 acen +chr22 14700000 17900000 q11.1 acen +chr22 17900000 22200000 q11.21 gneg +chr22 22200000 23500000 q11.22 gpos25 +chr22 23500000 25900000 q11.23 gneg +chr22 25900000 29600000 q12.1 gpos50 +chr22 29600000 32200000 q12.2 gneg +chr22 32200000 37600000 q12.3 gpos50 +chr22 37600000 41000000 q13.1 gneg +chr22 41000000 44200000 q13.2 gpos50 +chr22 44200000 48400000 q13.31 gneg +chr22 48400000 49400000 q13.32 gpos50 +chr22 49400000 51304566 q13.33 gneg +chrX 0 4300000 p22.33 gneg +chrX 4300000 6000000 p22.32 gpos50 +chrX 6000000 9500000 p22.31 gneg +chrX 9500000 17100000 p22.2 gpos50 +chrX 17100000 19300000 p22.13 gneg +chrX 19300000 21900000 p22.12 gpos50 +chrX 21900000 24900000 p22.11 gneg +chrX 24900000 29300000 p21.3 gpos100 +chrX 29300000 31500000 p21.2 gneg +chrX 31500000 37600000 p21.1 gpos100 +chrX 37600000 42400000 p11.4 gneg +chrX 42400000 46400000 p11.3 gpos75 +chrX 46400000 49800000 p11.23 gneg +chrX 49800000 54800000 p11.22 gpos25 +chrX 54800000 58100000 p11.21 gneg +chrX 58100000 60600000 p11.1 acen +chrX 60600000 63000000 q11.1 acen +chrX 63000000 64600000 q11.2 gneg +chrX 64600000 67800000 q12 gpos50 +chrX 67800000 71800000 q13.1 gneg +chrX 71800000 73900000 q13.2 gpos50 +chrX 73900000 76000000 q13.3 gneg +chrX 76000000 84600000 q21.1 gpos100 +chrX 84600000 86200000 q21.2 gneg +chrX 86200000 91800000 q21.31 gpos100 +chrX 91800000 93500000 q21.32 gneg +chrX 93500000 98300000 q21.33 gpos75 +chrX 98300000 102600000 q22.1 gneg +chrX 102600000 103700000 q22.2 gpos50 +chrX 103700000 108700000 q22.3 gneg +chrX 108700000 116500000 q23 gpos75 +chrX 116500000 120900000 q24 gneg +chrX 120900000 128700000 q25 gpos100 +chrX 128700000 130400000 q26.1 gneg +chrX 130400000 133600000 q26.2 gpos25 +chrX 133600000 138000000 q26.3 gneg +chrX 138000000 140300000 q27.1 gpos75 +chrX 140300000 142100000 q27.2 gneg +chrX 142100000 147100000 q27.3 gpos100 +chrX 147100000 155270560 q28 gneg +chrY 0 2500000 p11.32 gneg +chrY 2500000 3000000 p11.31 gpos50 +chrY 3000000 11600000 p11.2 gneg +chrY 11600000 12500000 p11.1 acen +chrY 12500000 13400000 q11.1 acen +chrY 13400000 15100000 q11.21 gneg +chrY 15100000 19800000 q11.221 gpos50 +chrY 19800000 22100000 q11.222 gneg +chrY 22100000 26200000 q11.223 gpos50 +chrY 26200000 28800000 q11.23 gneg +chrY 28800000 59373566 q12 gvar +chrM 0 16571 +chr1_gl000191_random 0 106433 +chr1_gl000192_random 0 547496 +chr4_ctg9_hap1 0 590426 +chr4_gl000193_random 0 189789 +chr4_gl000194_random 0 191469 +chr6_apd_hap1 0 4622290 +chr6_cox_hap2 0 4795371 +chr6_dbb_hap3 0 4610396 +chr6_mann_hap4 0 4683263 +chr6_mcf_hap5 0 4833398 +chr6_qbl_hap6 0 4611984 +chr6_ssto_hap7 0 4928567 +chr7_gl000195_random 0 182896 +chr8_gl000196_random 0 38914 +chr8_gl000197_random 0 37175 +chr9_gl000198_random 0 90085 +chr9_gl000199_random 0 169874 +chr9_gl000200_random 0 187035 +chr9_gl000201_random 0 36148 +chr11_gl000202_random 0 40103 +chr17_ctg5_hap1 0 1680828 +chr17_gl000203_random 0 37498 +chr17_gl000204_random 0 81310 +chr17_gl000205_random 0 174588 +chr17_gl000206_random 0 41001 +chr18_gl000207_random 0 4262 +chr19_gl000208_random 0 92689 +chr19_gl000209_random 0 159169 +chr21_gl000210_random 0 27682 +chrUn_gl000211 0 166566 +chrUn_gl000212 0 186858 +chrUn_gl000213 0 164239 +chrUn_gl000214 0 137718 +chrUn_gl000215 0 172545 +chrUn_gl000216 0 172294 +chrUn_gl000217 0 172149 +chrUn_gl000218 0 161147 +chrUn_gl000219 0 179198 +chrUn_gl000220 0 161802 +chrUn_gl000221 0 155397 +chrUn_gl000222 0 186861 +chrUn_gl000223 0 180455 +chrUn_gl000224 0 179693 +chrUn_gl000225 0 211173 +chrUn_gl000226 0 15008 +chrUn_gl000227 0 128374 +chrUn_gl000228 0 129120 +chrUn_gl000229 0 19913 +chrUn_gl000230 0 43691 +chrUn_gl000231 0 27386 +chrUn_gl000232 0 40652 +chrUn_gl000233 0 45941 +chrUn_gl000234 0 40531 +chrUn_gl000235 0 34474 +chrUn_gl000236 0 41934 +chrUn_gl000237 0 45867 +chrUn_gl000238 0 39939 +chrUn_gl000239 0 33824 +chrUn_gl000240 0 41933 +chrUn_gl000241 0 42152 +chrUn_gl000242 0 43523 +chrUn_gl000243 0 43341 +chrUn_gl000244 0 39929 +chrUn_gl000245 0 36651 +chrUn_gl000246 0 38154 +chrUn_gl000247 0 36422 +chrUn_gl000248 0 39786 +chrUn_gl000249 0 38502 \ No newline at end of file From 02cdd03ee9554f29a9983284e6289b8b35857eec Mon Sep 17 00:00:00 2001 From: kcleal Date: Fri, 21 Jun 2024 15:19:07 +0100 Subject: [PATCH 39/51] More work on ideogram. Main surface now externally managed for speed --- include/cytobands.h | 80 ------------------------------------------- include/ideogram.h | 21 ++++++++++++ src/drawing.cpp | 76 ++++++++++++++++++++++++++-------------- src/drawing.h | 7 ++-- src/hts_funcs.cpp | 4 +-- src/main.cpp | 24 ++++++++++++- src/parser.cpp | 2 +- src/plot_commands.cpp | 11 +++--- src/plot_controls.cpp | 34 ++++++++++++++---- src/plot_manager.cpp | 46 +++++++++++++++---------- src/plot_manager.h | 9 ++++- src/segments.cpp | 3 +- src/term_out.cpp | 13 +++++-- src/themes.cpp | 73 +++++++++++++++++++++++++++++++++++++++ src/themes.h | 8 +++++ src/utils.h | 2 ++ 16 files changed, 267 insertions(+), 146 deletions(-) delete mode 100644 include/cytobands.h create mode 100644 include/ideogram.h diff --git a/include/cytobands.h b/include/cytobands.h deleted file mode 100644 index 572b220..0000000 --- a/include/cytobands.h +++ /dev/null @@ -1,80 +0,0 @@ -// -// Created by Kez Cleal on 20/06/2024. -// - -#pragma once - -#include -#include -#include -#include -#include -#include - - -struct Band { - int start, end, alpha, red, green, blue; - std::string name; -}; - - -void printIdeogram(std::vector& bands) { - std::cout << "{\n"; - for (const auto& b : bands) { - std::cout << - "{" << b.start << ", " - << b.end << ", " - << b.alpha << ", " - << b.red << ", " - << b.green << ", " - << b.blue << ", " - << b.name << "}\n" - } - std::cout << "};\n\n"; -} - - -#define next std::getline(iss, token, '\t') - -void readCytoBandFile(std::string file_path, std::vector& ideogram) { - std::ifstream band_file(file_path); - if (!band_file) { - throw std::runtime_error("Failed to open input files"); - } - std::string line, token, name, property; - while (std::getline(band_file, line)) { - std::istringstream iss(line); - next; next; - int start = std::stoi(token); - next; - int end = std::stoi(token); - next; - name = token; - next; - property = name; - - if (property == "gneg") { - ideogram.emplace_back() = {start, end, 0, 0, 0, 0, name}; - } else if (property == "gpos25") { - ideogram.emplace_back() = {start, end, 0.5, 235, 235, 235, name}; - } else if (property == "gpos50") { - ideogram.emplace_back() = {start, end, 0.5, 185, 185, 185, name}; - } else if (property == "gpos75") { - ideogram.emplace_back() = {start, end, 0.5, 110, 110, 110, name}; - } else if (property == "gpos100") { - ideogram.emplace_back() = {start, end, 0.5, 60, 60, 60, name}; - } else if (property == "acen") { - ideogram.emplace_back() = {start, end, 0.5, 220, 10, 10, name}; - } else if (property == "gvar") { - ideogram.emplace_back() = {start, end, 0.5, 10, 10, 220, name}; - } else { // try custom color scheme - try { - std::string a = Utils::split(property, ','); - ideogram.emplace_back() = {start, end, std::stoi(a[0]), std::stoi(a[1]), std::stoi(a[2]), std::stoi(a[3]), name}; - } except { - }; - - } - } - printIdeogram(ideogram); -} \ No newline at end of file diff --git a/include/ideogram.h b/include/ideogram.h new file mode 100644 index 0000000..45f6c68 --- /dev/null +++ b/include/ideogram.h @@ -0,0 +1,21 @@ +// +// Created by Kez Cleal on 20/06/2024. +// + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + + + +namespace Ideo { + + + +} // namespace Cyto \ No newline at end of file diff --git a/src/drawing.cpp b/src/drawing.cpp index 8471e51..d131b42 100644 --- a/src/drawing.cpp +++ b/src/drawing.cpp @@ -1654,59 +1654,83 @@ namespace Drawing { } - void drawChromLocation(const Themes::IniOptions &opts, const std::vector &collections, + void drawChromLocation(const Themes::IniOptions &opts, + const std::vector ®ions, + const std::unordered_map> &ideogram, SkCanvas *canvas, - const faidx_t *fai, std::vector &headers, size_t nRegions, float fb_width, + const faidx_t *fai, float fb_width, float fb_height, float monitorScale) { - SkPaint paint, line; -// paint.setARGB(255, 110, 120, 165); - + SkPaint paint, light_paint, line; if (opts.theme_str == "igv") { paint.setARGB(255, 87, 95, 107); -// paint.setARGB(255, 160, 160, 165); } else { paint.setARGB(255, 149, 149, 163); -// paint.setColor(SK_ColorRED); } paint.setStrokeWidth(2); paint.setStyle(SkPaint::kStroke_Style); -// line.setColor((opts.theme_str == "dark") ? SK_ColorGRAY : SK_ColorBLACK); + + light_paint = opts.theme.lcLightJoins; + line.setColor((opts.theme_str == "dark") ? SK_ColorGRAY : SK_ColorBLACK); line.setStrokeWidth(monitorScale); line.setStyle(SkPaint::kStroke_Style); SkRect rect{}; SkPath path{}; - auto yh = std::fmax((float) (fb_height * 0.0175), 10 * monitorScale); - float rowHeight = (float) fb_height / (float) headers.size(); - float colWidth = (float) fb_width / (float) nRegions; + + float yh = std::fmax((float) (fb_height * 0.0175), 10 * monitorScale); + float yh_two_thirds = yh * (float)0.66; + float yh_one_third = yh * (float)0.33; + float yh_half = yh * (float)0.5; + + float top = fb_height - (yh * 2); + float colWidth = (float) fb_width / (float) regions.size(); float gap = 50; float gap2 = 2 * gap; float drawWidth = colWidth - gap2; + if (drawWidth < 0) { return; } - for (auto &cl: collections) { - if (cl.bamIdx + 1 != (int) headers.size()) { - continue; - } - auto length = (float) faidx_seq_len(fai, cl.region->chrom.c_str()); - float s = (float) cl.region->start / length; - float e = (float) cl.region->end / length; + + float regionIdx = 0; + for (const auto& region: regions) { + + auto length = (float) faidx_seq_len(fai, region.chrom.c_str()); + float s = (float) region.start / length; + float e = (float) region.end / length; float w = (e - s) * drawWidth; if (w < 3) { w = 3; } - float yp = ((cl.bamIdx + 1) * rowHeight) - yh - (yh * 0.5); - float xp = (cl.regionIdx * colWidth) + gap; - rect.setXYWH(xp + (s * drawWidth), - yp, - w, - yh); + float xp = (regionIdx * colWidth) + gap; + path.reset(); - path.moveTo(xp, ((cl.bamIdx + 1) * rowHeight) - (yh)); - path.lineTo(xp + drawWidth, ((cl.bamIdx + 1) * rowHeight) - (yh)); + path.moveTo(xp, top + yh_half); + path.lineTo(xp + drawWidth, top + yh_half); canvas->drawPath(path, line); + + auto it = ideogram.find(region.chrom); + if (it != ideogram.end()) { + const std::vector& bands = it->second; + for (const auto& b : bands) { + float sb = (float) b.start / length; + float eb = (float) b.end / length; + float wb = (eb - sb) * drawWidth; + rect.setXYWH(xp + (sb * drawWidth), + top + yh_one_third, + wb, + yh_two_thirds); + canvas->drawRect(rect, b.paint); + canvas->drawRect(rect, light_paint); + } + } + rect.setXYWH(xp + (s * drawWidth), + top, + w, + yh + yh_one_third); canvas->drawRect(rect, paint); + + regionIdx += 1; } } } \ No newline at end of file diff --git a/src/drawing.h b/src/drawing.h index 1a166f1..a46a81f 100644 --- a/src/drawing.h +++ b/src/drawing.h @@ -22,7 +22,6 @@ #include "BS_thread_pool.h" #include "ankerl_unordered_dense.h" #include "hts_funcs.h" - #include "utils.h" #include "segments.h" #include "themes.h" @@ -49,7 +48,9 @@ namespace Drawing { SkCanvas *canvas, float totalTabixY, float tabixY, std::vector &tracks, std::vector ®ions, const Themes::Fonts &fonts, float gap, float monitorScale); - void drawChromLocation(const Themes::IniOptions &opts, const std::vector &collections, SkCanvas* canvas, - const faidx_t* fai, std::vector &headers, size_t nRegions, float fb_width, float fb_height, float monitorScale); + void drawChromLocation(const Themes::IniOptions &opts, + const std::vector ®ions, + const std::unordered_map> &ideogram, SkCanvas* canvas, + const faidx_t* fai, float fb_width, float fb_height, float monitorScale); } \ No newline at end of file diff --git a/src/hts_funcs.cpp b/src/hts_funcs.cpp index 67dd14f..a21160d 100644 --- a/src/hts_funcs.cpp +++ b/src/hts_funcs.cpp @@ -190,12 +190,12 @@ namespace HGW { readQueue.pop_back(); } + Segs::init_parallel(readQueue, threads, pool); + if (!filters.empty()) { applyFilters(filters, readQueue, hdr_ptr, col.bamIdx, col.regionIdx); } - Segs::init_parallel(readQueue, threads, pool); - if (coverage) { int l_arr = (int)col.covArr.size() - 1; for (auto &i : readQueue) { diff --git a/src/main.cpp b/src/main.cpp index b1d02ae..9977ec0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -107,6 +107,9 @@ int main(int argc, char *argv[]) { program.add_argument("-f", "--file") .append() .help("Output single image to file"); + program.add_argument("--ideogram") + .append() + .help("Ideogram file"); program.add_argument("-n", "--no-show") .default_value(false).implicit_value(true) .help("Don't display images to screen"); @@ -523,8 +526,18 @@ int main(int argc, char *argv[]) { plotter.addFilter(s); } + if (program.is_used("--ideogram")) { + plotter.addIdeogram(program.get("--ideogram")); + } + + + // initialize drawing surface + sk_sp rasterSurface = SkSurface::MakeRasterN32Premul(iopts.dimensions.x, + iopts.dimensions.y); + plotter.rasterCanvas = rasterSurface->getCanvas(); + plotter.rasterSurfacePtr = &rasterSurface; - // initialize display screen + // initialize graphics window plotter.init(iopts.dimensions.x, iopts.dimensions.y); int fb_height, fb_width; glfwGetFramebufferSize(plotter.window, &fb_width, &fb_height); @@ -660,6 +673,9 @@ int main(int argc, char *argv[]) { for (auto &s: filters) { plotter.addFilter(s); } + if (program.is_used("--ideogram")) { + plotter.addIdeogram(program.get("--ideogram")); + } plotter.opts.theme.setAlphas(); @@ -775,6 +791,9 @@ int main(int argc, char *argv[]) { for (auto &s: filters) { m->addFilter(s); } + if (program.is_used("--ideogram")) { + m->addIdeogram(program.get("--ideogram")); + } managers.push_back(m); } @@ -887,6 +906,9 @@ int main(int argc, char *argv[]) { for (auto &s: filters) { m->addFilter(s); } + if (program.is_used("--ideogram")) { + m->addIdeogram(program.get("--ideogram")); + } managers.push_back(m); } diff --git a/src/parser.cpp b/src/parser.cpp index 4a6dbb6..5d57d85 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -492,7 +492,7 @@ namespace Parse { } } - bool Parser::eval(const Segs::Align &aln, const sam_hdr_t* hdr, int bamIdx, int regionIdx) { + bool Parser::eval(const Segs::Align& aln, const sam_hdr_t* hdr, int bamIdx, int regionIdx) { bool block_result = true; diff --git a/src/plot_commands.cpp b/src/plot_commands.cpp index b1a3eb3..965885c 100644 --- a/src/plot_commands.cpp +++ b/src/plot_commands.cpp @@ -38,7 +38,7 @@ namespace Commands { enum Err { - NONE, + NONE = 0, UNKNOWN, SILENT, @@ -1077,9 +1077,12 @@ namespace Commands { return reason; } - void handle_err(Err result, std::ostream& out) { + void save_command_or_handle_err(Err result, std::ostream& out, + std::vector* applied, std::string& command) { switch (result) { - case NONE: break; + case NONE: + applied->push_back(command); + break; case UNKNOWN: out << termcolor::red << "Error:" << termcolor::reset << " Unknown error\n"; break; @@ -1195,7 +1198,7 @@ namespace Commands { } else { res = infer_region_or_feature(p, command, parts); } + save_command_or_handle_err(res, out, &p->commandsApplied, command); p->inputText = ""; - handle_err(res, out); } } \ No newline at end of file diff --git a/src/plot_controls.cpp b/src/plot_controls.cpp index d028816..99af7b6 100644 --- a/src/plot_controls.cpp +++ b/src/plot_controls.cpp @@ -1322,9 +1322,20 @@ namespace Manager { while (bnd != cl.readQueue.begin()) { if (!opts.tlen_yscale) { if (bnd->y == level && (int)bnd->pos <= pos && pos < (int)bnd->reference_end) { - bnd->edge_type = 4; - target_qname = bam_get_qname(bnd->delegate); - Term::printRead(bnd, headers[cl.bamIdx], selectedAlign, cl.region->refSeq, cl.region->start, cl.region->end, opts.low_memory, out); + if (bnd->edge_type == 4) { + if (bnd->has_SA || bnd->delegate->core.flag & 2048) { + bnd->edge_type = 2; // "SPLIT" + } else if (bnd->delegate->core.flag & 8) { + bnd->edge_type = 3; // "MATE_UNMAPPED" + } else { + bnd->edge_type = 1; // "NORMAL" + } + target_qname = ""; + } else { + bnd->edge_type = 4; + target_qname = bam_get_qname(bnd->delegate); + Term::printRead(bnd, headers[cl.bamIdx], selectedAlign, cl.region->refSeq, cl.region->start, cl.region->end, opts.low_memory, out); + } redraw = true; processed = true; cl.skipDrawingReads = false; @@ -1333,9 +1344,20 @@ namespace Manager { } } else { if ((bnd->y >= level - slop && bnd->y < level) && (int)bnd->pos <= pos && pos < (int)bnd->reference_end) { - bnd->edge_type = 4; - target_qname = bam_get_qname(bnd->delegate); - Term::printRead(bnd, headers[cl.bamIdx], selectedAlign, cl.region->refSeq, cl.region->start, cl.region->end, opts.low_memory, out); + if (bnd->edge_type == 4) { + if (bnd->has_SA || bnd->delegate->core.flag & 2048) { + bnd->edge_type = 2; // "SPLIT" + } else if (bnd->delegate->core.flag & 8) { + bnd->edge_type = 3; // "MATE_UNMAPPED" + } else { + bnd->edge_type = 1; // "NORMAL" + } + target_qname = ""; + } else { + bnd->edge_type = 4; + target_qname = bam_get_qname(bnd->delegate); + Term::printRead(bnd, headers[cl.bamIdx], selectedAlign, cl.region->refSeq, cl.region->start, cl.region->end, opts.low_memory, out); + } redraw = true; processed = true; cl.skipDrawingReads = false; diff --git a/src/plot_manager.cpp b/src/plot_manager.cpp index 6d5574a..b8daf2d 100644 --- a/src/plot_manager.cpp +++ b/src/plot_manager.cpp @@ -172,6 +172,7 @@ namespace Manager { this->rasterSurface = SkSurface::MakeRasterDirect( info, &pixelMemory[0], rowBytes); rasterCanvas = rasterSurface->getCanvas(); + rasterSurfacePtr = &rasterSurface; return pixelMemory.size(); } @@ -258,7 +259,9 @@ namespace Manager { glfwMakeContextCurrent(window); setGlfwFrameBufferSize(); - makeRasterSurface(); + if (rasterSurfacePtr == nullptr) { + makeRasterSurface(); + } } @@ -330,7 +333,6 @@ namespace Manager { inLabels, sLabels) ); - std::cout << "plot_manager YO: " << variantFilename << std::endl; } void GwPlot::setOutLabelFile(const std::string &path) { @@ -379,6 +381,10 @@ namespace Manager { f.close(); } + void GwPlot::addIdeogram(std::string path) { + Themes::readIdeogramFile(path, ideogram); + } + void GwPlot::addFilter(std::string &filter_str) { Parse::Parser p = Parse::Parser(outStr); if (p.set_filter(filter_str, bams.size(), regions.size()) > 0) { @@ -517,7 +523,7 @@ namespace Manager { } int xpos, ypos; glfwGetWindowPos(window, &xpos, &ypos); - opts.saveCurrentSession(reference, bam_paths, track_paths, regions, variant_paths_info, commandHistory, + opts.saveCurrentSession(reference, bam_paths, track_paths, regions, variant_paths_info, commandsApplied, "", mode, xpos, ypos, monitorScale); } @@ -565,6 +571,7 @@ namespace Manager { // if (opts.low_mem) { // drawScreenNoBuffer(sSurface->getCanvas(), sContext, sSurface); // } else { + drawScreen(sSurface->getCanvas(), sContext, sSurface); // } @@ -586,8 +593,6 @@ namespace Manager { setScaling(); bboxes = Utils::imageBoundingBoxes(opts.number, (float)fb_width, (float)fb_height); -// opts.dimensions.x = fb_width; -// opts.dimensions.y = fb_height; resizeTriggered = false; sContext->abandonContext(); @@ -616,14 +621,19 @@ namespace Manager { std::exit(-1); } -// SkImageInfo info = SkImageInfo::MakeN32Premul(opts.dimensions.x * monitorScale, opts.dimensions.y * monitorScale); - SkImageInfo info = SkImageInfo::MakeN32Premul(fb_width, fb_height); - size_t rowBytes = info.minRowBytes(); - size_t size = info.computeByteSize(rowBytes); - pixelMemory.resize(size); - rasterSurface = SkSurface::MakeRasterDirect( - info, &pixelMemory[0], rowBytes); +// SkImageInfo info = SkImageInfo::MakeN32Premul(fb_width, fb_height); +// size_t rowBytes = info.minRowBytes(); +// size_t size = info.computeByteSize(rowBytes); +// pixelMemory.resize(size); +// rasterSurface = SkSurface::MakeRasterDirect( +// info, &pixelMemory[0], rowBytes); +// rasterCanvas = rasterSurface->getCanvas(); + + + rasterSurface = SkSurface::MakeRasterN32Premul(fb_width,fb_height); rasterCanvas = rasterSurface->getCanvas(); + rasterSurfacePtr = &rasterSurface; + resizeTimer = std::chrono::high_resolution_clock::now(); } @@ -893,11 +903,11 @@ namespace Manager { Drawing::drawRef(opts, regions, fb_width, canvasR, fonts, refSpace, (float)regions.size(), gap); Drawing::drawBorders(opts, fb_width, fb_height, canvasR, regions.size(), bams.size(), trackY, covY, (int)tracks.size(), totalTabixY, refSpace, gap); Drawing::drawTracks(opts, fb_width, fb_height, canvasR, totalTabixY, tabixY, tracks, regions, fonts, gap, monitorScale); - Drawing::drawChromLocation(opts, collections, canvasR, fai, headers, regions.size(), fb_width, fb_height, monitorScale); + Drawing::drawChromLocation(opts, regions, ideogram, canvasR, fai, fb_width, fb_height, monitorScale); } - imageCacheQueue.emplace_back(frameId, rasterSurface->makeImageSnapshot()); + imageCacheQueue.emplace_back(frameId, rasterSurfacePtr[0]->makeImageSnapshot()); canvas->drawImage(imageCacheQueue.back().second, 0, 0); sContext->flush(); @@ -914,7 +924,7 @@ namespace Manager { } else { runDrawNoBuffer(); } - imageCacheQueue.emplace_back(frameId, rasterSurface->makeImageSnapshot()); + imageCacheQueue.emplace_back(frameId, rasterSurfacePtr[0]->makeImageSnapshot()); canvas->drawImage(imageCacheQueue.back().second, 0, 0); sContext->flush(); glfwSwapBuffers(window); @@ -1369,7 +1379,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, collections, canvas, fai, headers, regions.size(), fb_width, fb_height, monitorScale); + Drawing::drawChromLocation(opts, regions, ideogram, canvas, fai, fb_width, fb_height, monitorScale); // std::cerr << " time runDrawNoBuffer " << (std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - initial).count()) << std::endl; } @@ -1381,12 +1391,12 @@ namespace Manager { sk_sp GwPlot::makeImage() { makeRasterSurface(); runDraw(); - sk_sp img(rasterSurface->makeImageSnapshot()); + sk_sp img(rasterSurfacePtr[0]->makeImageSnapshot()); return img; } void GwPlot::rasterToPng(const char* path) { - sk_sp img(rasterSurface->makeImageSnapshot()); + sk_sp img(rasterSurfacePtr[0]->makeImageSnapshot()); if (!img) { return; } sk_sp png(img->encodeToData()); if (!png) { return; } diff --git a/src/plot_manager.h b/src/plot_manager.h index 1e3593b..ba2ed01 100644 --- a/src/plot_manager.h +++ b/src/plot_manager.h @@ -113,6 +113,8 @@ namespace Manager { std::vector variantTracks; // make image tiles from these + std::vector< std::string > commandHistory, commandsApplied; + HGW::GwVariantTrack *currentVarTrack; // var track with current focus/event int mouseOverTileIndex; // tile with mouse over @@ -134,6 +136,7 @@ namespace Manager { GLFWwindow* backWindow; sk_sp rasterSurface; + sk_sp* rasterSurfacePtr; // option to use externally managed surface (faster) SkCanvas* rasterCanvas; Show mode; @@ -165,6 +168,8 @@ namespace Manager { void addVariantTrack(std::string &path, int startIndex, bool cacheStdin, bool useFullPath); + void addIdeogram(std::string path); + void addFilter(std::string &filter_str); void setOutLabelFile(const std::string &path); @@ -235,7 +240,7 @@ namespace Manager { bool captureText, shiftPress, ctrlPress, processText; bool tabBorderPress; - std::vector< std::string > commandHistory; + int commandIndex, charIndex; float totalCovY, covY, totalTabixY, tabixY, trackY, regionWidth, bamHeight, refSpace, sliderSpace; @@ -259,6 +264,8 @@ namespace Manager { std::vector bboxes; + std::unordered_map> ideogram; + BS::thread_pool pool; void drawScreen(SkCanvas* canvas, GrDirectContext* sContext, SkSurface *sSurface); diff --git a/src/segments.cpp b/src/segments.cpp index c9166ad..ddf1b41 100644 --- a/src/segments.cpp +++ b/src/segments.cpp @@ -253,8 +253,9 @@ namespace Segs { u, u, u, u, u, u, u, u, INV_F}; - void align_init(Align *self) { //noexcept { + void align_init(Align *self) { // auto start = std::chrono::high_resolution_clock::now(); + bam1_t *src = self->delegate; self->pos = src->core.pos; diff --git a/src/term_out.cpp b/src/term_out.cpp index 2c2a268..e4812a7 100644 --- a/src/term_out.cpp +++ b/src/term_out.cpp @@ -113,15 +113,22 @@ namespace Term { " RG, BC, BX, RX, LB, MD, MI, PU, SA, MC, NM, CM, FI, HO, MQ, SM, TC, UQ, AS\n\n" " These can be combined with '{operator}' values:\n" " &, ==, !=, >, <, >=, <=, eq, ne, gt, lt, ge, le, contains, omit\n\n" + " Reads can be filtered on their mapping orientation/pattern e.g:\n" + " filter pattern == del # deletion-like pattern\n" + " filter pattern == inv_f # inversion-forward\n" + " filter pattern == inv_f # inversion-reverse\n" + " filter pattern != tra # translocation\n\n" " Bitwise flags can also be applied with named values:\n" " paired, proper-pair, unmapped, munmap, reverse, mreverse, read1, read2, secondary, dup, supplementary\n\n" + " filter paired\n" + " filter read1\n\n" " Expressions can be chained together providing all expressions are 'AND' or 'OR' blocks:\n" " filter mapq >= 20 and mapq < 30\n" " filter mapq >= 20 or flag & supplementary\n\n" " Finally, you can apply filters to specific panels using array indexing notation:\n" - " filter mapq > 0 [:, 0] # All rows, column 0 (all bams, first region only)\n" - " filter mapq > 0 [0, :] # Row 0, all columns (the first bam only, all regions)\n" - " filter mapq > 0 [1, -1] # Row 1, last column\n\n"; + " filter mapq > 0 [:, 0] # All rows, column 0 (all bams, first region only)\n" + " filter mapq > 0 [0, :] # Row 0, all columns (the first bam only, all regions)\n" + " filter mapq > 0 [1, -1] # Row 1, last column\n\n"; } else if (s == "find" || s == "f") { out << " Find a read with name.\n All alignments with the same name will be highlighted with a black border\n Examples:\n 'find D00360:18:H8VC6ADXX:1:1107:5538:24033'\n\n"; } else if (s == "goto") { diff --git a/src/themes.cpp b/src/themes.cpp index 0c7b3f8..e06a0f5 100644 --- a/src/themes.cpp +++ b/src/themes.cpp @@ -842,4 +842,77 @@ namespace Themes { } } } + + void printIdeogram(const std::unordered_map> &bands) { + std::cout << "{\n"; + for (const auto &kv: bands) { + std::cout << "{" << kv.first << ",\n{ \n"; + for (const auto &b: kv.second) { + std::cout << + " {" << b.start << ", " + << b.end << ", " + << b.alpha << ", " + << b.red << ", " + << b.green << ", " + << b.blue << ", " + << b.name << "},\n"; + } + std::cout << " },\n"; + } + std::cout << "};\n\n"; + } + + void readIdeogramFile(std::string file_path, std::unordered_map> &ideogram) { + std::ifstream band_file(file_path); + if (!band_file) { + throw std::runtime_error("Failed to open input files"); + } + std::string line, token, chrom, name, property; + while (std::getline(band_file, line)) { + std::istringstream iss(line); +#define next_t std::getline(iss, token, '\t') + next_t; + chrom = token; + next_t; + int start = std::stoi(token); + next_t; + int end = std::stoi(token); + next_t; + name = token; + next_t; + property = token; + + if (property == "gneg") { + ideogram[chrom].emplace_back() = {start, end, 255, 255, 255, 255, {}, name}; + } else if (property == "gpos25") { + ideogram[chrom].emplace_back() = {start, end, 255, 235, 235, 235, {}, name}; + } else if (property == "gpos50") { + ideogram[chrom].emplace_back() = {start, end, 255, 185, 185, 185, {}, name}; + } else if (property == "gpos75") { + ideogram[chrom].emplace_back() = {start, end, 255, 110, 110, 110, {}, name}; + } else if (property == "gpos100") { + ideogram[chrom].emplace_back() = {start, end, 255, 60, 60, 60, {}, name}; + } else if (property == "acen") { + ideogram[chrom].emplace_back() = {start, end, 255, 220, 10, 10, {}, name}; + } else if (property == "gvar") { + ideogram[chrom].emplace_back() = {start, end, 255, 10, 10, 220, {}, name}; + } else if (!property.empty()) { // try custom color scheme + std::vector a = Utils::split(property, ','); + if (a.size() == 4) { + try { + ideogram[chrom].emplace_back() = {start, end, std::stoi(a[0]), std::stoi(a[1]), std::stoi(a[2]), + std::stoi(a[3]), {}, name}; + } catch (...) { + + } + } + } + } + for (auto& kv : ideogram) { + for (auto& i : kv.second) { + i.paint.setARGB(i.alpha, i.red, i.green, i.blue); + } + } +// printIdeogram(ideogram); + } } diff --git a/src/themes.h b/src/themes.h index 557309d..e7f74ca 100644 --- a/src/themes.h +++ b/src/themes.h @@ -200,4 +200,12 @@ namespace Themes { void setFontSize(float yScaling, float yScale); }; + struct Band { + int start, end, alpha, red, green, blue; + SkPaint paint; + std::string name; + }; + + void readIdeogramFile(std::string file_path, std::unordered_map> &ideogram); + } diff --git a/src/utils.h b/src/utils.h index 2b63155..3783d45 100644 --- a/src/utils.h +++ b/src/utils.h @@ -9,6 +9,7 @@ #include #include "ankerl_unordered_dense.h" #include "export_definitions.h" +#include "ideogram.h" #include "htslib/faidx.h" #if defined(_WIN32) @@ -159,4 +160,5 @@ namespace Utils { void ltrim(std::string &s); void rtrim(std::string &s); void trim(std::string &s); + } From 8bde3e736de40c7f46e0580244fe9d426888a2bc Mon Sep 17 00:00:00 2001 From: kcleal Date: Mon, 24 Jun 2024 15:08:58 +0100 Subject: [PATCH 40/51] Custom ideograms from bed files. Added colour command. Added roi command. Fixed scrolling bug. --- src/drawing.cpp | 55 +++++++++---------- src/hts_funcs.cpp | 2 - src/hts_funcs.h | 4 +- src/main.cpp | 2 +- src/plot_commands.cpp | 120 ++++++++++++++++++++++++++++++++++++++++++ src/plot_controls.cpp | 32 ++++++----- src/plot_manager.cpp | 13 +++-- src/term_out.cpp | 48 ++++++++++++++++- src/themes.cpp | 54 +++++++++++++------ src/themes.h | 10 ++-- 10 files changed, 272 insertions(+), 68 deletions(-) diff --git a/src/drawing.cpp b/src/drawing.cpp index d131b42..e92aae1 100644 --- a/src/drawing.cpp +++ b/src/drawing.cpp @@ -69,7 +69,7 @@ namespace Drawing { path.lineTo(markerP, rp + (refSpace*0.7)); path.lineTo(markerP + xp, rp); path.lineTo(markerP, rp); - canvas->drawPath(path, theme.marker_paint); + canvas->drawPath(path, theme.fcMarkers); } float markerP2 = (cl.xScaling * (float) (cl.region->markerPosEnd - cl.region->start)) + cl.xOffset; if (markerP2 > cl.xOffset && markerP2 < (cl.regionPixels + cl.xOffset)) { @@ -79,7 +79,7 @@ namespace Drawing { path.lineTo(markerP2, rp + (refSpace*0.7)); path.lineTo(markerP2 + xp, rp); path.lineTo(markerP2, rp); - canvas->drawPath(path, theme.marker_paint); + canvas->drawPath(path, theme.fcMarkers); } } @@ -1317,16 +1317,20 @@ namespace Drawing { float y, float h, float stepX, float stepY, float gap, float gap2, float xScaling, float t, Themes::IniOptions &opts, SkCanvas *canvas, const Themes::Fonts &fonts, bool add_text, bool add_rect, bool v_line, bool shaded, float *labelsEnd, std::string &vartype, - float monitorScale, std::vector &text, bool addArc) { + float monitorScale, std::vector &text, bool addArc, bool isRoi) { float x = 0; float w; SkPaint faceColour, arcColour; - if (shaded) { - faceColour = opts.theme.fcCoverage; + if (isRoi) { + faceColour = opts.theme.fcRoi; } else { - faceColour = opts.theme.fcTrack; + if (shaded) { + faceColour = opts.theme.fcCoverage; + } else { + faceColour = opts.theme.fcTrack; + } } if (!vartype.empty()) { @@ -1470,7 +1474,7 @@ namespace Drawing { if (any_text) { drawTrackBlock(trk.start, trk.end, trk.name, rgn, rect, path, padX, padY, y, h, stepX, stepY, gap, gap2, xScaling, t, - opts, canvas, fonts, true, false, false, false, labelsEnd, empty_str, 0, text, false); + opts, canvas, fonts, true, false, false, false, labelsEnd, empty_str, 0, text, false, false); } for (int i = 0; i < target; ++i) { int s, e; @@ -1491,21 +1495,17 @@ namespace Drawing { if (s < trk.coding_end && e > trk.coding_end) { //overlaps, split in to two blocks! drawTrackBlock(s, trk.coding_end, trk.name, rgn, rect, path, padX, padY, y, h, stepX, stepY, gap, - gap2, xScaling, t, - opts, canvas, fonts, false, true, add_line, false, labelsEnd, empty_str, 0, text, false); + gap2, xScaling, t,opts, canvas, fonts, false, true, add_line, false, labelsEnd, empty_str, 0, text, false, false); drawTrackBlock(trk.coding_end, e, trk.name, rgn, rect, path, padX, padY, y, h, stepX, stepY, gap, - gap2, xScaling, t, - opts, canvas, fonts, false, true, add_line, true, labelsEnd, empty_str, 0, text, false); + gap2, xScaling, t,opts, canvas, fonts, false, true, add_line, true, labelsEnd, empty_str, 0, text, false, false); } else if (thickness == 1) { drawTrackBlock(s, e, trk.name, rgn, rect, path, padX, padY, y, h, stepX, stepY, gap, gap2, xScaling, - t, - opts, canvas, fonts, false, true, add_line, true, labelsEnd, empty_str, 0, text, false); + t,opts, canvas, fonts, false, true, add_line, true, labelsEnd, empty_str, 0, text, false, false); } else { drawTrackBlock(s, e, trk.name, rgn, rect, path, padX, padY, y, h, stepX, stepY, gap, gap2, xScaling, - t, - opts, canvas, fonts, false, true, add_line, false, labelsEnd, empty_str, 0, text, false); + t,opts, canvas, fonts, false, true, add_line, false, labelsEnd, empty_str, 0, text, false, false); } } float x, yy, w; @@ -1582,7 +1582,6 @@ namespace Drawing { float right = ((float) (rgn.end - rgn.start) * xScaling) + padX; canvas->save(); canvas->clipRect({padX, y + padY, right, y + padY + stepY}, false); - trk.fetch(&rgn); if (trk.kind == HGW::BIGWIG) { drawTrackBigWig(trk, rgn, rect, padX, padY, y + (stepY * trackIdx), stepX, stepY, gap, gap2, @@ -1599,7 +1598,6 @@ namespace Drawing { std::vector &features = rgn.featuresInView[trackIdx]; features.clear(); - if (isGFF) { HGW::collectGFFTrackData(trk, features); } else { @@ -1636,7 +1634,7 @@ namespace Drawing { drawTrackBlock(f.start, f.end, f.name, rgn, rect, path, padX, padY_track, y, h, stepX, stepY, gap, gap2, xScaling, t, opts, canvas, fonts, any_text, true, add_line, false, fLevelEnd, f.vartype, monitorScale, - text, opts.sv_arcs); + text, opts.sv_arcs, trk.kind == HGW::FType::ROI); } } trackIdx += 1; @@ -1646,6 +1644,7 @@ namespace Drawing { canvas->drawTextBlob(t.text, t.x, t.y, opts.theme.tcDel); } } + canvas->restore(); } padX += stepX; @@ -1661,11 +1660,12 @@ namespace Drawing { const faidx_t *fai, float fb_width, float fb_height, float monitorScale) { SkPaint paint, light_paint, line; - if (opts.theme_str == "igv") { - paint.setARGB(255, 87, 95, 107); - } else { - paint.setARGB(255, 149, 149, 163); - } + paint.setARGB(255, 240, 32, 73); +// if (opts.theme_str == "igv") { +// paint.setARGB(255, 87, 95, 107); +// } else { +// paint.setARGB(255, 149, 149, 163); +// } paint.setStrokeWidth(2); paint.setStyle(SkPaint::kStroke_Style); @@ -1680,7 +1680,6 @@ namespace Drawing { float yh = std::fmax((float) (fb_height * 0.0175), 10 * monitorScale); float yh_two_thirds = yh * (float)0.66; float yh_one_third = yh * (float)0.33; - float yh_half = yh * (float)0.5; float top = fb_height - (yh * 2); float colWidth = (float) fb_width / (float) regions.size(); @@ -1705,8 +1704,8 @@ namespace Drawing { float xp = (regionIdx * colWidth) + gap; path.reset(); - path.moveTo(xp, top + yh_half); - path.lineTo(xp + drawWidth, top + yh_half); + path.moveTo(xp, top + yh_two_thirds ); + path.lineTo(xp + drawWidth, top + yh_two_thirds); canvas->drawPath(path, line); auto it = ideogram.find(region.chrom); @@ -1721,7 +1720,9 @@ namespace Drawing { wb, yh_two_thirds); canvas->drawRect(rect, b.paint); - canvas->drawRect(rect, light_paint); + if (wb > 2) { + canvas->drawRect(rect, light_paint); + } } } rect.setXYWH(xp + (s * drawWidth), diff --git a/src/hts_funcs.cpp b/src/hts_funcs.cpp index a21160d..c5c66b1 100644 --- a/src/hts_funcs.cpp +++ b/src/hts_funcs.cpp @@ -1535,7 +1535,6 @@ namespace HGW { if (tp[0] == '#') { continue; } - std::cerr << tp << std::endl; std::vector parts = Utils::split_keep_empty_str(tp, '\t'); chrom = parts[0]; chrom2 = chrom; @@ -2192,7 +2191,6 @@ namespace HGW { b->end = trk.stop; b->line = trk.variantString; b->parts = trk.parts; -// b->parent = trk.parent; b->anyToDraw = true; if (trk.parts.size() >= 5) { b->strand = (trk.parts[5] == "+") ? 1 : (trk.parts[5] == "-") ? -1 : 0; diff --git a/src/hts_funcs.h b/src/hts_funcs.h index 27556ae..2dd0314 100644 --- a/src/hts_funcs.h +++ b/src/hts_funcs.h @@ -41,6 +41,7 @@ namespace HGW { GTF_NOI, GW_LABEL, STDIN, + ROI, }; void guessRefGenomeFromBam(std::string &inputName, Themes::IniOptions &opts, std::vector &bam_paths, std::vector ®ions); @@ -131,6 +132,7 @@ namespace HGW { /* * VCF/BCF/BED/GFF3/LABEL file reader. No label parsing for vcf/bcf. * Non-indexed files are cached using TrackBlock items. Files with an index are fetched during drawing. + * Can also have no file associated with it, just an array of TrackBlock (used for roi drawing) */ class GwTrack { public: @@ -206,7 +208,7 @@ namespace HGW { enum TrackType { VCF, GW_TRACK, - IMAGES + IMAGES, }; class GwVariantTrack { public: diff --git a/src/main.cpp b/src/main.cpp index 9977ec0..4a4a74c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -109,7 +109,7 @@ int main(int argc, char *argv[]) { .help("Output single image to file"); program.add_argument("--ideogram") .append() - .help("Ideogram file"); + .help("Ideogram bed file (uncompressed). Any bed file should work"); program.add_argument("-n", "--no-show") .default_value(false).implicit_value(true) .help("Don't display images to screen"); diff --git a/src/plot_commands.cpp b/src/plot_commands.cpp index 965885c..6fb25e4 100644 --- a/src/plot_commands.cpp +++ b/src/plot_commands.cpp @@ -1077,6 +1077,123 @@ namespace Commands { return reason; } + Err update_colour(Plot* p, std::string& command, std::vector parts, std::ostream& out) { + if (parts.size() != 6) { + return Err::OPTION_NOT_UNDERSTOOD; + } + int alpha, red, green, blue; + try { + alpha = std::stoi(parts[2]); + red = std::stoi(parts[3]); + green = std::stoi(parts[4]); + blue = std::stoi(parts[5]); + } catch (...) { + return Err::OPTION_NOT_UNDERSTOOD; + } + Themes::GwPaint e; + std::string &c = parts[1]; + if (c == "bgPaint") { e = Themes::GwPaint::bgPaint; } + else if (c == "fcNormal") { e = Themes::GwPaint::fcNormal; } + else if (c == "fcDel") { e = Themes::GwPaint::fcDel; } + else if (c == "fcDup") { e = Themes::GwPaint::fcDup; } + else if (c == "fcInvF") { e = Themes::GwPaint::fcInvF; } + else if (c == "fcInvR") { e = Themes::GwPaint::fcInvR; } + else if (c == "fcTra") { e = Themes::GwPaint::fcTra; } + else if (c == "fcIns") { e = Themes::GwPaint::fcIns; } + else if (c == "fcSoftClip") { e = Themes::GwPaint::fcSoftClip; } + else if (c == "fcA") { e = Themes::GwPaint::fcA; } + else if (c == "fcT") { e = Themes::GwPaint::fcT; } + else if (c == "fcC") { e = Themes::GwPaint::fcC; } + else if (c == "fcG") { e = Themes::GwPaint::fcG; } + else if (c == "fcN") { e = Themes::GwPaint::fcN; } + else if (c == "fcCoverage") { e = Themes::GwPaint::fcCoverage; } + else if (c == "fcTrack") { e = Themes::GwPaint::fcTrack; } + else if (c == "fcNormal0") { e = Themes::GwPaint::fcNormal0; } + else if (c == "fcDel0") { e = Themes::GwPaint::fcDel0; } + else if (c == "fcDup0") { e = Themes::GwPaint::fcDup0; } + else if (c == "fcInvF0") { e = Themes::GwPaint::fcInvF0; } + else if (c == "fcInvR0") { e = Themes::GwPaint::fcInvR0; } + else if (c == "fcTra0") { e = Themes::GwPaint::fcTra0; } + else if (c == "fcSoftClip0") { e = Themes::GwPaint::fcSoftClip0; } + else if (c == "fcBigWig") { e = Themes::GwPaint::fcBigWig; } + else if (c == "mate_fc") { e = Themes::GwPaint::mate_fc; } + else if (c == "mate_fc0") { e = Themes::GwPaint::mate_fc0; } + else if (c == "ecMateUnmapped") { e = Themes::GwPaint::ecMateUnmapped; } + else if (c == "ecSplit") { e = Themes::GwPaint::ecSplit; } + else if (c == "ecSelected") { e = Themes::GwPaint::ecSelected; } + else if (c == "lcJoins") { e = Themes::GwPaint::lcJoins; } + else if (c == "lcCoverage") { e = Themes::GwPaint::lcCoverage; } + else if (c == "lcLightJoins") { e = Themes::GwPaint::lcLightJoins; } + else if (c == "lcLabel") { e = Themes::GwPaint::lcLabel; } + else if (c == "lcBright") { e = Themes::GwPaint::lcBright; } + else if (c == "tcDel") { e = Themes::GwPaint::tcDel; } + else if (c == "tcIns") { e = Themes::GwPaint::tcIns; } + else if (c == "tcLabels") { e = Themes::GwPaint::tcLabels; } + else if (c == "tcBackground") { e = Themes::GwPaint::tcBackground; } + else if (c == "fcMarkers") { e = Themes::GwPaint::fcMarkers; } + else if (c == "fcRoi") { e = Themes::GwPaint::fcRoi; } + else { + return Err::OPTION_NOT_UNDERSTOOD; + } + p->opts.theme.setPaintARGB(e, alpha, red, green, blue); + return Err::NONE; + } + + Err add_roi(Plot* p, std::string& command, std::vector parts, std::ostream& out) { + if (p->regions.empty()) { + return Err::SILENT; + } + std::string com = command; + Utils::TrackBlock b; + b.line = command; + b.strand = 0; + Utils::Region& rgn = p->regions[p->regionSelection]; + if (command == "roi") { // use active region as roi + b.chrom = rgn.chrom; + b.start = rgn.start; + b.end = rgn.end; + b.name = rgn.toString(); + } else { + bool good = false; + int s_idx = 4; + try { + Utils::Region r = Utils::parseRegion(parts[1]); + if (r.chrom == rgn.chrom) { + b.chrom = r.chrom; + b.start = r.start; + b.end = r.end; + good = true; + s_idx += parts[1].size(); + } + } catch (...) { + } + if (!good) { + b.chrom = rgn.chrom; + b.start = rgn.start; + b.end = rgn.end; + } + com.erase(0, s_idx); + b.name = com; + } + bool added = false; + for (auto& t : p->tracks) { + if (t.kind == HGW::FType::ROI) { + t.allBlocks[b.chrom].add(b.start, b.end, b); + t.allBlocks[b.chrom].index(); + added = true; + break; + } + } + if (!added) { + p->tracks.emplace_back() = HGW::GwTrack(); + p->tracks.back().kind = HGW::FType::ROI; + p->tracks.back().add_to_dict = true; + p->tracks.back().allBlocks[b.chrom].add(b.start, b.end, b); + p->tracks.back().allBlocks[b.chrom].index(); + } + return Err::NONE; + } + void save_command_or_handle_err(Err result, std::ostream& out, std::vector* applied, std::string& command) { switch (result) { @@ -1166,6 +1283,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); }}, {"count", PARAMS { return count(p, command, out); }}, {"filter", PARAMS { return addFilter(p, command, out); }}, diff --git a/src/plot_controls.cpp b/src/plot_controls.cpp index 99af7b6..7ad75e7 100644 --- a/src/plot_controls.cpp +++ b/src/plot_controls.cpp @@ -714,8 +714,8 @@ namespace Manager { if (region.refSeq != nullptr) { delete region.refSeq; } - region.start = region.start + shift; - region.end = region.end + shift; + region.start = std::max(0, region.start + shift); + region.end = std::max(region.start + 1, region.end + shift); fetchRefSeq(region); for (auto &cl : collections) { if (cl.regionIdx == regionSelection) { @@ -750,8 +750,8 @@ namespace Manager { if (region.refSeq != nullptr) { delete region.refSeq; } - region.start = region.start - shift; - region.end = region.end - shift; + region.start = std::max(0, region.start - shift); + region.end = std::max(region.start + 1, region.end - shift); fetchRefSeq(region); for (auto &cl : collections) { if (cl.regionIdx == regionSelection) { @@ -787,8 +787,8 @@ namespace Manager { if (region.refSeq != nullptr) { delete region.refSeq; } - region.start = region.start - shift_left; - region.end = region.end + shift; + region.start = std::max(0, region.start - shift_left); + region.end = std::max(region.start + 1, region.end + shift); fetchRefSeq(region); for (auto &cl : collections) { if (cl.regionIdx == regionSelection) { @@ -840,8 +840,8 @@ namespace Manager { if (region.refSeq != nullptr) { delete region.refSeq; } - region.start = region.start + shift; - region.end = region.end - shift; + region.start = std::max(0, region.start + shift); + region.end = std::max(region.start + 1, region.end - shift); fetchRefSeq(region); for (auto &cl : collections) { if (cl.regionIdx == regionSelection) { @@ -1113,7 +1113,7 @@ namespace Manager { relX = (relX > drawWidth) ? drawWidth : relX; float relP = relX / drawWidth; auto length = (float)faidx_seq_len(fai, rgn.chrom.c_str()); - auto new_s = (int)(length * relP); + auto new_s = std::max(0, (int)(length * relP)); int regionL = rgn.end - rgn.start; rgn.start = new_s; rgn.end = new_s + regionL; @@ -1289,8 +1289,8 @@ namespace Manager { int strt = pos - 2500; strt = (strt < 0) ? 0 : strt; Utils::Region ®ion = regions[regionSelection]; - region.start = strt; - region.end = strt + 5000; + region.start = std::max(0, strt); + region.end = std::max(region.start + 1, strt + 5000); regionSelection = cl.regionIdx; delete region.refSeq; fetchRefSeq(region); @@ -1729,9 +1729,11 @@ namespace Manager { int travel = (int) (w * (xDrag / windowW)); if (region.start - travel < 1) { return; - } else { + } else if (clicked.start - travel > 0) { region.start = clicked.start - travel; region.end = clicked.end - travel; + } else { + return; } if (region.start < 1 || region.end < 1) { return; @@ -1925,5 +1927,11 @@ namespace Manager { resizeTimer = std::chrono::high_resolution_clock::now(); glfwGetFramebufferSize(window, &fb_width, &fb_height); bboxes = Utils::imageBoundingBoxes(opts.number, (float)fb_width, (float)fb_height); + if (opts.theme_str == "igv") { + glClearColor(1.0f, 1.0f, 1.0f, 1.0f); + } else { + glClearColor(0.f, 0.f, 0.f, 1.0f); + } + glClear(GL_COLOR_BUFFER_BIT); } } diff --git a/src/plot_manager.cpp b/src/plot_manager.cpp index b8daf2d..59ae417 100644 --- a/src/plot_manager.cpp +++ b/src/plot_manager.cpp @@ -109,7 +109,7 @@ namespace Manager { std::cerr << "Warning: track file does not exists - " << trk_item << std::endl; } } else { - tracks.push_back(HGW::GwTrack()); + tracks.emplace_back() = HGW::GwTrack(); tracks.back().genome_tag = opts.genome_tag; tracks.back().open(trk_item, true); tracks.back().variant_distance = &opts.variant_distance; @@ -119,7 +119,7 @@ namespace Manager { tracks.reserve(track_paths.size()); } for (const auto &tp: track_paths) { - tracks.push_back(HGW::GwTrack()); + tracks.emplace_back() = HGW::GwTrack(); tracks.back().open(tp, true); tracks.back().variant_distance = &opts.variant_distance; } @@ -515,7 +515,9 @@ namespace Manager { void GwPlot::saveSession() { std::vector track_paths; for (const auto& item: tracks) { - track_paths.push_back(item.path); + if (item.kind != HGW::FType::ROI) { + track_paths.push_back(item.path); + } } std::vector> variant_paths_info; for (const auto& item: variantTracks) { @@ -566,6 +568,7 @@ namespace Manager { if (delay > 0) { std::this_thread::sleep_for(std::chrono::milliseconds(delay)); } + if (redraw) { if (mode == Show::SINGLE) { // if (opts.low_mem) { @@ -584,6 +587,10 @@ namespace Manager { sContext->flush(); glfwSwapBuffers(window); +// if (resizeTriggered && !imageCacheQueue.empty()) { +// sSurface->getCanvas()->drawImage(imageCacheQueue.back().second, 0, 0); +// } + if (resizeTriggered && std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - resizeTimer) > 100ms) { imageCache.clear(); redraw = true; diff --git a/src/term_out.cpp b/src/term_out.cpp index e4812a7..5c83313 100644 --- a/src/term_out.cpp +++ b/src/term_out.cpp @@ -34,6 +34,7 @@ namespace Term { out << termcolor::underline << "\nCommand Modifier Description \n" << termcolor::reset; out << termcolor::green << "[locus] " << termcolor::reset << "e.g. 'chr1' or 'chr1:1-20000'\n"; out << termcolor::green << "add region(s) " << termcolor::reset << "Add one or more regions e.g. 'add chr1:1-20000'\n"; + out << termcolor::green << "colour name + ARGB " << termcolor::reset << "Set a colour for a plot component\n"; out << termcolor::green << "count expression? " << termcolor::reset << "Count reads. See filter for example expressions'\n"; out << termcolor::green << "cov value? " << termcolor::reset << "Change max coverage value. Use 'cov' to toggle coverage\n"; out << termcolor::green << "edges " << termcolor::reset << "Toggle edges\n"; @@ -54,6 +55,7 @@ namespace Term { out << termcolor::green << "quit, q - " << termcolor::reset << "Quit GW\n"; out << termcolor::green << "refresh, r - " << termcolor::reset << "Refresh and re-draw the window\n"; 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 (.xml) to file\n"; out << termcolor::green << "settings " << termcolor::reset << "Open the settings menu'\n"; @@ -91,8 +93,48 @@ namespace Term { out << " Navigate to a genomic locus.\n You can use chromosome names or chromosome coordinates.\n Examples:\n 'chr1:1-20000', 'chr1', 'chr1:10000'\n\n"; } else if (s == "add") { out << " Add a genomic locus.\n This will add a new locus to the right-hand-side of your view.\n Examples:\n 'add chr1:1-20000', 'add chr2'\n\n"; -// } else if (s == "config") { -// out << " Open the GW config file.\n The config file will be opened in a text editor, for Mac TextEdit will be used, linux will be vi, and windows will be notepad.\n\n"; + } else if (s == "colour" || s == "color") { + out << " Set the (alpha, red, green, blue) colour for one of the plot elements.\n" + " Elements are selected by name (see below) and values are in the range [0, 255].\n" + " For example 'colour fcNormal 255 255 0 0' sets face-colour of normal reads to red.\n\n" + " bgPaint - background paint\n" + " fcNormal - face-colour normal reads\n" + " fcDel - face-colour deletion pattern reads\n" + " fcDup - face-colour duplication pattern reads\n" + " fcInvF - face-colour inversion-forward pattern reads\n" + " fcInvR - face-colour inversion-reverse pattern reads\n" + " fcTra - face-colour translocation pattern reads\n" + " fcIns - face-colour insertion blocks\n" + " fcSoftClips - face-colour soft-clips when zoomed-out\n" + " fcA - face-colour A mismatch\n" + " fcT - face-colour T mismatch\n" + " fcC - face-colour C mismatch\n" + " fcG - face-colour G mismatch\n" + " fcN - face-colour N mismatch\n" + " fcCoverage - face-colour coverage track\n" + " fcTrack - face-colour tracks\n" + " fcBigWig - face-colour bigWig files\n" + " fcNormal0 - face-colour normal reads with mapq=0\n" + " fcDel0 - face-colour deletion pattern reads with mapq=0\n" + " fcDup0 - face-colour duplication pattern reads with mapq=0\n" + " fcInvF0 - face-colour inversion-forward pattern reads with mapq=0\n" + " fcInvR0 - face-colour inversion-reverse pattern reads with mapq=0\n" + " fcTra0 - face-colour translocation pattern reads with mapq=0\n" + " fcIns0 - face-colour insertion blocks with mapq=0\n" + " fcSoftClips0 - face-colour soft-clips when zoomed-out with mapq=0\n" + " fcMarkers - face-colour of markers\n" + " ecMateUnmapped - edge-colour mate unmapped reads\n" + " ecSplit - edge-colour split reads\n" + " ecSelected - edge-colour selected reads\n" + " lcJoins - line-colour of joins\n" + " lcCoverage - line-colour of coverage profile\n" + " lcLightJoins - line-colour of lighter joins\n" + " lcLabel - line-colour of labels\n" + " lcBright - line-colour of bright edges\n" + " tcDel - text-colour of deletions\n" + " tcIns - text-colour of insertions\n" + " tcLabels - text-colour of labels\n" + " tcBackground - text-colour of background\n\n"; } else if (s == "count") { out << " Count the visible reads in each view.\n A summary output will be displayed for each view on the screen.\n Optionally a filter expression may be added to the command. See the man page for 'filter' for mote details\n Examples:\n 'count', 'count flag & 2', 'count flag & proper-pair' \n\n"; } else if (s == "cov") { @@ -168,6 +210,8 @@ namespace Term { out << " Refresh the drawing.\n All filters will be removed any everything will be redrawn.\n\n"; } else if (s == "remove" || s == "rm") { out << " Remove a region, bam or track.\n Remove a region, bam or track by index. To remove a bam or track add a 'bam' or 'track' prefix.\n Examples:\n 'rm 0', 'rm bam1', 'rm track2'\n\n"; + } else if (s == "roi") { + out << " Add a region of interest as a new track. If no region is supplied, the visible active window is used\n Examples:\n 'roi', 'roi chr1:1-20000'\n\n"; } 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") { diff --git a/src/themes.cpp b/src/themes.cpp index e06a0f5..c994d57 100644 --- a/src/themes.cpp +++ b/src/themes.cpp @@ -89,8 +89,11 @@ namespace Themes { alpha = 204; mapq0_alpha = 102; - marker_paint.setStyle(SkPaint::kStrokeAndFill_Style); - marker_paint.setStrokeWidth(3); + fcMarkers.setStyle(SkPaint::kStrokeAndFill_Style); + fcMarkers.setStrokeWidth(3); + + fcRoi.setARGB(180, 230, 10, 45); + fcRoi.setStyle(SkPaint::kStrokeAndFill_Style); fcBigWig.setARGB(255, 20, 90, 190); fcBigWig.setStyle(SkPaint::kStrokeAndFill_Style); @@ -145,10 +148,10 @@ namespace Themes { insS.setStyle(SkPaint::kStroke_Style); insS.setStrokeWidth(4); - marker_paint.setStyle(SkPaint::kStrokeAndFill_Style); - marker_paint.setAntiAlias(true); - marker_paint.setStrokeMiter(0.1); - marker_paint.setStrokeWidth(0.5); + fcMarkers.setStyle(SkPaint::kStrokeAndFill_Style); + fcMarkers.setAntiAlias(true); + fcMarkers.setStrokeMiter(0.1); + fcMarkers.setStrokeWidth(0.5); for (size_t i=0; i < mate_fc.size(); ++i) { SkPaint p = mate_fc[i]; @@ -219,15 +222,14 @@ namespace Themes { case GwPaint::lcJoins: this->lcJoins.setARGB(a, r, g, b); break; case GwPaint::lcCoverage: this->lcCoverage.setARGB(a, r, g, b); break; case GwPaint::lcLightJoins: this->lcLightJoins.setARGB(a, r, g, b); break; - case GwPaint::insF: this->insF.setARGB(a, r, g, b); break; - case GwPaint::insS: this->insS.setARGB(a, r, g, b); break; case GwPaint::lcLabel: this->lcLabel.setARGB(a, r, g, b); break; case GwPaint::lcBright: this->lcBright.setARGB(a, r, g, b); break; case GwPaint::tcDel: this->tcDel.setARGB(a, r, g, b); break; case GwPaint::tcIns: this->tcIns.setARGB(a, r, g, b); break; case GwPaint::tcLabels: this->tcLabels.setARGB(a, r, g, b); break; case GwPaint::tcBackground: this->tcBackground.setARGB(a, r, g, b); break; - case GwPaint::marker_paint: this->marker_paint.setARGB(a, r, g, b); break; + case GwPaint::fcMarkers: this->fcMarkers.setARGB(a, r, g, b); break; + case GwPaint::fcRoi: this->fcRoi.setARGB(a, r, g, b); break; default: break; } } @@ -258,7 +260,7 @@ namespace Themes { tcLabels.setARGB(255, 80, 80, 80); tcIns.setARGB(255, 255, 255, 255); tcBackground.setARGB(255, 255, 255, 255); - marker_paint.setARGB(255, 0, 0, 0); + fcMarkers.setARGB(255, 0, 0, 0); ecSelected.setARGB(255, 0, 0, 0); ecSelected.setStyle(SkPaint::kStroke_Style); ecSelected.setStrokeWidth(2); @@ -295,7 +297,7 @@ namespace Themes { tcLabels.setARGB(255, 0, 0, 0); tcIns.setARGB(255, 227, 227, 227); tcBackground.setARGB(255, 10, 10, 20); - marker_paint.setARGB(255, 220, 220, 220); + fcMarkers.setARGB(255, 220, 220, 220); ecSelected.setARGB(255, 255, 255, 255); ecSelected.setStyle(SkPaint::kStroke_Style); ecSelected.setStrokeWidth(2); @@ -331,7 +333,7 @@ namespace Themes { tcLabels.setARGB(255, 100, 100, 100); tcIns.setARGB(255, 227, 227, 227); tcBackground.setARGB(255, 10, 10, 20); - marker_paint.setARGB(255, 220, 220, 220); + fcMarkers.setARGB(255, 220, 220, 220); ecSelected.setARGB(255, 255, 255, 255); ecSelected.setStyle(SkPaint::kStroke_Style); ecSelected.setStrokeWidth(2); @@ -738,7 +740,7 @@ namespace Themes { } count += 1; } - keep = {"filter ", "find ", "f "}; + keep = {"filter ", "find ", "f ", "colour", "color", "roi"}; size_t j = 0; for (; last_refresh < commands.size(); ++last_refresh) { for (const auto& k: keep) { @@ -867,10 +869,23 @@ namespace Themes { if (!band_file) { throw std::runtime_error("Failed to open input files"); } + std::unordered_map custom; std::string line, token, chrom, name, property; while (std::getline(band_file, line)) { std::istringstream iss(line); -#define next_t std::getline(iss, token, '\t') + if (line[0] == '#') { + if (Utils::startsWith(line, "#gw ")) { + std::vector parts = Utils::split( line, ' '); + if (parts.size() == 3) { + std::vector c = Utils::split(parts.back(), ','); + if (c.size() == 4) { + custom[parts[1]] = {0, 0, std::stoi(c[0]), std::stoi(c[1]), std::stoi(c[2]), std::stoi(c[3]), {}, parts[1]}; + } + } + } + continue; + } + #define next_t std::getline(iss, token, '\t') next_t; chrom = token; next_t; @@ -881,7 +896,6 @@ namespace Themes { name = token; next_t; property = token; - if (property == "gneg") { ideogram[chrom].emplace_back() = {start, end, 255, 255, 255, 255, {}, name}; } else if (property == "gpos25") { @@ -896,6 +910,11 @@ namespace Themes { ideogram[chrom].emplace_back() = {start, end, 255, 220, 10, 10, {}, name}; } else if (property == "gvar") { ideogram[chrom].emplace_back() = {start, end, 255, 10, 10, 220, {}, name}; + } else if (custom.find(name) != custom.end()) { + Band cust = custom[name]; + cust.start = start; + cust.end = end; + ideogram[chrom].emplace_back() = cust; } else if (!property.empty()) { // try custom color scheme std::vector a = Utils::split(property, ','); if (a.size() == 4) { @@ -906,6 +925,11 @@ namespace Themes { } } + else { + ideogram[chrom].emplace_back() = {start, end, 255, 85, 171, 159, {}, name}; + } + } else { + ideogram[chrom].emplace_back() = {start, end, 255, 85, 171, 159, {}, name}; } } for (auto& kv : ideogram) { diff --git a/src/themes.h b/src/themes.h index e7f74ca..09f548b 100644 --- a/src/themes.h +++ b/src/themes.h @@ -70,9 +70,9 @@ namespace Themes { enum GwPaint { bgPaint, fcNormal, fcDel, fcDup, fcInvF, fcInvR, fcTra, fcIns, fcSoftClip, fcA, fcT, fcC, fcG, fcN, fcCoverage, fcTrack, fcNormal0, fcDel0, fcDup0, fcInvF0, fcInvR0, fcTra0, - fcSoftClip0, fcBigWig, mate_fc, mate_fc0, ecMateUnmapped, ecSplit, ecSelected, - lcJoins, lcCoverage, lcLightJoins, insF, insS, lcLabel, lcBright, tcDel, tcIns, tcLabels, tcBackground, - marker_paint + fcSoftClip0, fcBigWig, fcRoi, mate_fc, mate_fc0, ecMateUnmapped, ecSplit, ecSelected, + lcJoins, lcCoverage, lcLightJoins, lcLabel, lcBright, tcDel, tcIns, tcLabels, tcBackground, + fcMarkers }; class EXPORT BaseTheme { @@ -83,7 +83,7 @@ namespace Themes { std::string name; // face colours SkPaint bgPaint, fcNormal, fcDel, fcDup, fcInvF, fcInvR, fcTra, fcIns, fcSoftClip, \ - fcA, fcT, fcC, fcG, fcN, fcCoverage, fcTrack; + fcA, fcT, fcC, fcG, fcN, fcCoverage, fcTrack, fcRoi; SkPaint fcNormal0, fcDel0, fcDup0, fcInvF0, fcInvR0, fcTra0, fcSoftClip0, fcBigWig; std::vector mate_fc; @@ -102,7 +102,7 @@ namespace Themes { SkPaint tcDel, tcIns, tcLabels, tcBackground; // Markers - SkPaint marker_paint; + SkPaint fcMarkers; uint8_t alpha, mapq0_alpha; From 9f4719de5f09c943bb12e8544bdbcad9a6d3fc3e Mon Sep 17 00:00:00 2001 From: kcleal Date: Tue, 25 Jun 2024 13:24:18 +0100 Subject: [PATCH 41/51] Work on tooltip --- src/drawing.cpp | 25 ++++++++++++------------ src/plot_controls.cpp | 4 ++-- src/plot_manager.cpp | 44 +++++++++++++++++++++++++++++++++++++++++++ src/plot_manager.h | 5 ++++- src/utils.h | 2 ++ 5 files changed, 64 insertions(+), 16 deletions(-) diff --git a/src/drawing.cpp b/src/drawing.cpp index e92aae1..75f2062 100644 --- a/src/drawing.cpp +++ b/src/drawing.cpp @@ -1677,15 +1677,15 @@ namespace Drawing { SkRect rect{}; SkPath path{}; - float yh = std::fmax((float) (fb_height * 0.0175), 10 * monitorScale); - float yh_two_thirds = yh * (float)0.66; - float yh_one_third = yh * (float)0.33; + const float yh = std::fmax((float) (fb_height * 0.0175), 10 * monitorScale); + const float yh_two_thirds = yh * (float)0.66; + const float yh_one_third = yh * (float)0.33; - float top = fb_height - (yh * 2); - float colWidth = (float) fb_width / (float) regions.size(); - float gap = 50; - float gap2 = 2 * gap; - float drawWidth = colWidth - gap2; + const float top = fb_height - (yh * 2); + const float colWidth = (float) fb_width / (float) regions.size(); + const float gap = 50; + const float gap2 = 100; + const float drawWidth = colWidth - gap2; if (drawWidth < 0) { return; @@ -1694,9 +1694,8 @@ namespace Drawing { float regionIdx = 0; for (const auto& region: regions) { - auto length = (float) faidx_seq_len(fai, region.chrom.c_str()); - float s = (float) region.start / length; - float e = (float) region.end / length; + float s = (float)region.start / (float)region.chromLength; + float e = (float)region.end / (float)region.chromLength; float w = (e - s) * drawWidth; if (w < 3) { w = 3; @@ -1712,8 +1711,8 @@ namespace Drawing { if (it != ideogram.end()) { const std::vector& bands = it->second; for (const auto& b : bands) { - float sb = (float) b.start / length; - float eb = (float) b.end / length; + float sb = (float) b.start / (float)region.chromLength; + float eb = (float) b.end / (float)region.chromLength; float wb = (eb - sb) * drawWidth; rect.setXYWH(xp + (sb * drawWidth), top + yh_one_third, diff --git a/src/plot_controls.cpp b/src/plot_controls.cpp index 7ad75e7..1ee5f23 100644 --- a/src/plot_controls.cpp +++ b/src/plot_controls.cpp @@ -1636,8 +1636,8 @@ namespace Manager { lastX = xPos; lastY = yPos; - double xPos_fb = xPos; - double yPos_fb = yPos; + xPos_fb = xPos; + yPos_fb = yPos; double xPosOri_fb = xOri; double yPosOri_fb = yOri; convertScreenCoordsToFrameBufferCoords(wind, &xPos_fb, &yPos_fb, fb_width, fb_height); diff --git a/src/plot_manager.cpp b/src/plot_manager.cpp index 59ae417..9c4c279 100644 --- a/src/plot_manager.cpp +++ b/src/plot_manager.cpp @@ -36,6 +36,7 @@ #include "menu.h" #include "segments.h" #include "termcolor.h" +#include "term_out.h" #include "themes.h" #include "window_icon.h" @@ -80,6 +81,8 @@ namespace Manager { textFromSettings = false; terminalOutput = true; monitorScale = 1; + xPos_fb = 0; + yPos_fb = 0; fonts = Themes::Fonts(); fonts.setTypeface(opt.font_str, opt.font_size); if (!reference.empty()) { @@ -294,6 +297,9 @@ namespace Manager { if (rgn.end - rgn.start < opts.snp_threshold) { fetchRefSeq(rgn); } + if (rgn.chromLength == 0) { + rgn.chromLength = faidx_seq_len(fai, rgn.chrom.c_str()); + } } } @@ -938,6 +944,41 @@ namespace Manager { redraw = false; } + void GwPlot::drawCursorPosOnRefSlider(SkCanvas *canvas) { + // determine if cursor is over the ref slider + if (regions.empty() || xPos_fb <= 0 || yPos_fb <= 0 || regionSelection < 0) { + return; + } + const float yh = std::fmax((float) (fb_height * 0.0175), 10 * monitorScale); + if (yPos_fb < fb_height - (yh * 2)) { + return; + } + const float colWidth = (float) fb_width / (float) regions.size(); + const float gap = 50; + const float gap2 = 100; + const float drawWidth = colWidth - gap2; + float xp = ((float)regionSelection * colWidth) + gap; + if (xPos_fb < xp || xPos_fb > xp + drawWidth) { + return; + } + int pos = (int)(((xPos_fb - xp) / drawWidth) * (float)regions[regionSelection].chromLength); + std::string s = Term::intToStringCommas(pos); + + float estimatedTextWidth = (float) s.size() * fonts.overlayWidth; + SkRect rect; + SkPaint rect_paint = opts.theme.bgPaint; + rect_paint.setAlpha(160); + float xbox = std::fmin(xPos_fb + monitorScale, drawWidth); + rect.setXYWH(xbox, fb_height - (yh *2) + (monitorScale), estimatedTextWidth, (yh*(float)1.33) - monitorScale - monitorScale); + canvas->drawRect(rect, rect_paint); + sk_sp blob = SkTextBlob::MakeFromString(s.c_str(), fonts.overlay); + SkPath path; + path.moveTo(xPos_fb, fb_height - (yh * 2)); + path.lineTo(xPos_fb, fb_height - (yh * (float)0.66)); + canvas->drawPath(path, opts.theme.lcBright); + canvas->drawTextBlob(blob, xbox + monitorScale, fb_height - yh, opts.theme.tcDel); + } + void GwPlot::drawOverlay(SkCanvas *canvas) { if (!imageCacheQueue.empty()) { while (imageCacheQueue.front().first != frameId) { @@ -945,6 +986,9 @@ namespace Manager { } canvas->drawImage(imageCacheQueue.back().second, 0, 0); } + if (mode == Show::SINGLE) { + drawCursorPosOnRefSlider(canvas); + } if (mode == Show::SETTINGS) { if (!imageCacheQueue.empty()) { while (imageCacheQueue.front().first != frameId) { diff --git a/src/plot_manager.h b/src/plot_manager.h index ba2ed01..491e6cd 100644 --- a/src/plot_manager.h +++ b/src/plot_manager.h @@ -76,7 +76,8 @@ namespace Manager { std::vector &track_paths); ~GwPlot(); - int fb_width, fb_height; + int fb_width, fb_height; // frame buffer size + double xPos_fb, yPos_fb; // mouse position in frame buffer coords float monitorScale, gap; int samMaxY; int regionSelection, variantFileSelection; @@ -290,6 +291,8 @@ namespace Manager { void updateSlider(float xPos); + void drawCursorPosOnRefSlider(SkCanvas *canvas); + }; void imageToPng(sk_sp &img, std::filesystem::path &outdir); diff --git a/src/utils.h b/src/utils.h index 3783d45..b833c1d 100644 --- a/src/utils.h +++ b/src/utils.h @@ -76,6 +76,7 @@ namespace Utils { std::string chrom; int start, end; int markerPos, markerPosEnd; + int chromLength; const char *refSeq; std::vector refSeq_nibbled; std::vector> featuresInView; // one vector for each Track @@ -86,6 +87,7 @@ namespace Utils { end = -1; markerPos = -1; markerPosEnd = -1; + chromLength = 0; refSeq = nullptr; } std::string toString(); From 7ea13afeb3c2bd771e043d9a6a8c2c0a5d707738 Mon Sep 17 00:00:00 2001 From: kcleal Date: Fri, 28 Jun 2024 14:48:24 +0100 Subject: [PATCH 42/51] Started work on mod display. Faster drawing around ~5-7% --- .gw.ini | 1 + src/drawing.cpp | 131 ++++++++------ src/hts_funcs.cpp | 13 +- src/hts_funcs.h | 3 +- src/main.cpp | 19 +- src/plot_commands.cpp | 4 +- src/plot_controls.cpp | 2 +- src/plot_manager.cpp | 3 +- src/segments.cpp | 162 +++++++++++------- src/segments.h | 21 ++- src/themes.cpp | 7 +- src/themes.h | 2 +- ...hr20:13829780-13866370_mod_call_sample.bam | Bin 0 -> 107862 bytes ...:13829780-13866370_mod_call_sample.bam.bai | Bin 0 -> 6864 bytes 14 files changed, 229 insertions(+), 139 deletions(-) create mode 100644 test/chr20:13829780-13866370_mod_call_sample.bam create mode 100644 test/chr20:13829780-13866370_mod_call_sample.bam.bai diff --git a/.gw.ini b/.gw.ini index 01357d7..480fa75 100644 --- a/.gw.ini +++ b/.gw.ini @@ -37,6 +37,7 @@ tabix_track_height=0.3 font=Menlo font_size=14 sv_arcs=true +show_mods=true session_file= [view_thresholds] diff --git a/src/drawing.cpp b/src/drawing.cpp index e92aae1..d6fbbe6 100644 --- a/src/drawing.cpp +++ b/src/drawing.cpp @@ -443,6 +443,40 @@ namespace Drawing { canvas->drawPath(path, sidesColor); } + constexpr char lookup_ref_base[256] = { + ['A'] = 1, ['a'] = 1, + ['C'] = 2, ['c'] = 2, + ['G'] = 4, ['g'] = 4, + ['T'] = 8, ['t'] = 8, + ['N'] = 15, ['n'] = 15, // All other entries default to 15 + }; + + void update_A(Segs::Mismatches& elem) { elem.A += 1; } + void update_C(Segs::Mismatches& elem) { elem.C += 1; } + void update_G(Segs::Mismatches& elem) { elem.G += 1; } + void update_T(Segs::Mismatches& elem) { elem.T += 1; } + void update_pass(Segs::Mismatches& elem) {} // For N bases + + // Lookup table for function pointers, initialized statically + void (*lookup_table_mm[16])(Segs::Mismatches&) = { + update_pass, // 0 + update_A, // 1 + update_C, // 2 + update_pass, // 3 + update_G, // 4 + update_pass, // 5 + update_pass, // 6 + update_pass, // 7 + update_T, // 8 + update_pass, // 9 + update_pass, // 10 + update_pass, // 11 + update_pass, // 12 + update_pass, // 13 + update_pass, // 14 + update_pass // 15 + }; + void drawMismatchesNoMD(SkCanvas *canvas, SkRect &rect, const Themes::BaseTheme &theme, const Utils::Region *region, const Segs::Align &align, float width, float xScaling, float xOffset, float mmPosOffset, float yScaledOffset, @@ -481,7 +515,7 @@ namespace Drawing { switch (op) { case BAM_CMATCH: for (uint32_t i = 0; i < l; ++i) { - int r_idx = (int) r_pos - rbegin; // Casting once and reusing. + int r_idx = (int) r_pos - rbegin; if (r_idx < 0) { idx += 1; r_pos += 1; @@ -496,28 +530,7 @@ namespace Drawing { char ref_base; char current_base = refSeq[r_idx]; - // Using a lookup might be faster if 'switch-case' doesn't optimize well in the compiler. - switch (current_base) { - case 'A': - case 'a': - ref_base = 1; - break; - case 'C': - case 'c': - ref_base = 2; - break; - case 'G': - case 'g': - ref_base = 4; - break; - case 'T': - case 't': - ref_base = 8; - break; - default: - ref_base = 15; - break; // assuming 'N' and other characters map to 15 - } + ref_base = lookup_ref_base[(unsigned char)current_base]; char bam_base = bam_seqi(ptr_seq, idx); if (bam_base != ref_base) { @@ -526,23 +539,26 @@ namespace Drawing { rect.setXYWH(p + precalculated_xOffset_mmPosOffset, yScaledOffset, width, pH); canvas->drawRect(rect, theme.BasePaints[bam_base][colorIdx]); if (!collection_processed) { - auto &mismatch = mm_array[r_pos - rbegin]; // Reduce redundant calculations - switch (bam_base) { - case 1: - mismatch.A += 1; - break; - case 2: - mismatch.C += 1; - break; - case 4: - mismatch.G += 1; - break; - case 8: - mismatch.T += 1; - break; - default: - break; - } + lookup_table_mm[(size_t)bam_base](mm_array[r_pos - rbegin]); + +// auto &mismatch = mm_array[r_pos - rbegin]; +// +// switch (bam_base) { +// case 1: +// mismatch.A += 1; +// break; +// case 2: +// mismatch.C += 1; +// break; +// case 4: +// mismatch.G += 1; +// break; +// case 8: +// mismatch.T += 1; +// break; +// default: +// break; +// } } } idx += 1; @@ -566,23 +582,26 @@ namespace Drawing { rect.setXYWH(p + precalculated_xOffset_mmPosOffset, yScaledOffset, width, pH); canvas->drawRect(rect, theme.BasePaints[bam_base][colorIdx]); - auto &mismatch = mm_array[r_pos - rbegin]; // Reduce redundant calculations - switch (bam_base) { - case 1: - mismatch.A += 1; - break; - case 2: - mismatch.C += 1; - break; - case 4: - mismatch.G += 1; - break; - case 8: - mismatch.T += 1; - break; - default: - break; + if (!collection_processed) { + lookup_table_mm[(size_t) bam_base](mm_array[r_pos - rbegin]); } +// auto &mismatch = mm_array[r_pos - rbegin]; // Reduce redundant calculations +// switch (bam_base) { +// case 1: +// mismatch.A += 1; +// break; +// case 2: +// mismatch.C += 1; +// break; +// case 4: +// mismatch.G += 1; +// break; +// case 8: +// mismatch.T += 1; +// break; +// default: +// break; +// } } idx += 1; r_pos += 1; diff --git a/src/hts_funcs.cpp b/src/hts_funcs.cpp index c5c66b1..b25c683 100644 --- a/src/hts_funcs.cpp +++ b/src/hts_funcs.cpp @@ -159,7 +159,8 @@ namespace HGW { void collectReadsAndCoverage(Segs::ReadCollection &col, htsFile *b, sam_hdr_t *hdr_ptr, hts_idx_t *index, int threads, Utils::Region *region, - bool coverage, std::vector &filters, BS::thread_pool &pool) { + bool coverage, std::vector &filters, BS::thread_pool &pool, + const bool parse_mods) { bam1_t *src; hts_itr_t *iter_q; int tid = sam_hdr_name2tid(hdr_ptr, region->chrom.c_str()); @@ -190,7 +191,7 @@ namespace HGW { readQueue.pop_back(); } - Segs::init_parallel(readQueue, threads, pool); + Segs::init_parallel(readQueue, threads, pool, parse_mods); if (!filters.empty()) { applyFilters(filters, readQueue, hdr_ptr, col.bamIdx, col.regionIdx); @@ -379,7 +380,7 @@ namespace HGW { if (j < BATCH) { continue; } - Segs::init_parallel(readQueue, threads, pool); + Segs::init_parallel(readQueue, threads, pool, opts.parse_mods); if (filter) { applyFilters_noDelete(filters, readQueue, hdr_ptr, col.bamIdx, col.regionIdx); } @@ -403,7 +404,7 @@ namespace HGW { if (j < BATCH) { readQueue.erase(readQueue.begin() + j, readQueue.end()); if (!readQueue.empty()) { - Segs::init_parallel(readQueue, threads, pool); + Segs::init_parallel(readQueue, threads, pool, opts.parse_mods); if (!filters.empty()) { applyFilters_noDelete(filters, readQueue, hdr_ptr, col.bamIdx, col.regionIdx); } @@ -455,7 +456,7 @@ namespace HGW { if (src->core.flag & 4 || src->core.n_cigar == 0) { continue; } - Segs::align_init(&readQueue.back()); + Segs::align_init(&readQueue.back(), opts.parse_mods); if (filter) { applyFilters_noDelete(filters, readQueue, hdr_ptr, col.bamIdx, col.regionIdx); if (readQueue.back().y == -2) { @@ -670,7 +671,7 @@ namespace HGW { if (!filters.empty()) { applyFilters(filters, newReads, hdr_ptr, col.bamIdx, col.regionIdx); } - Segs::init_parallel(newReads, opts.threads, pool); + Segs::init_parallel(newReads, opts.threads, pool, opts.parse_mods); bool findYall = false; if (col.vScroll == 0 && opts.link_op == 0) { // only new reads need findY, otherwise, reset all below int maxY = Segs::findY(col, newReads, opts.link_op, opts, region, left); diff --git a/src/hts_funcs.h b/src/hts_funcs.h index 2dd0314..08aa9a5 100644 --- a/src/hts_funcs.h +++ b/src/hts_funcs.h @@ -91,7 +91,8 @@ namespace HGW { void collectReadsAndCoverage(Segs::ReadCollection &col, htsFile *bam, sam_hdr_t *hdr_ptr, hts_idx_t *index, int threads, Utils::Region *region, bool coverage, - std::vector &filters, BS::thread_pool &pool); + std::vector &filters, BS::thread_pool &pool, + const bool parse_mods); void iterDrawParallel(Segs::ReadCollection &col, htsFile *b, diff --git a/src/main.cpp b/src/main.cpp index 4a4a74c..cce4651 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -183,6 +183,9 @@ int main(int argc, char *argv[]) { program.add_argument("--no-soft-clips") .default_value(false).implicit_value(true) .help("Soft-clips are not shown"); + program.add_argument("--no-mods") + .default_value(false).implicit_value(true) + .help("Base modifications are not shown"); program.add_argument("--low-mem") .default_value(false).implicit_value(true) .help("Reduce memory usage by discarding quality values"); @@ -486,6 +489,9 @@ int main(int argc, char *argv[]) { if (program.is_used("--no-soft-clips")) { iopts.soft_clip_threshold = 0; } + if (program.is_used("--no-mods")) { + iopts.parse_mods = 0; + } if (program.is_used("--start-index")) { iopts.start_index = program.get("--start-index"); } @@ -530,13 +536,6 @@ int main(int argc, char *argv[]) { plotter.addIdeogram(program.get("--ideogram")); } - - // initialize drawing surface - sk_sp rasterSurface = SkSurface::MakeRasterN32Premul(iopts.dimensions.x, - iopts.dimensions.y); - plotter.rasterCanvas = rasterSurface->getCanvas(); - plotter.rasterSurfacePtr = &rasterSurface; - // initialize graphics window plotter.init(iopts.dimensions.x, iopts.dimensions.y); int fb_height, fb_width; @@ -595,6 +594,12 @@ int main(int argc, char *argv[]) { std::exit(-1); } + // initialize drawing surface + sk_sp rasterSurface = SkSurface::MakeRasterN32Premul(iopts.dimensions.x * plotter.monitorScale, + iopts.dimensions.y * plotter.monitorScale); + plotter.rasterCanvas = rasterSurface->getCanvas(); + plotter.rasterSurfacePtr = &rasterSurface; + // start UI here if (!program.is_used("--variants") && !program.is_used("--images")) { int res = plotter.startUI(sContext, sSurface, program.get("--delay")); // plot regions diff --git a/src/plot_commands.cpp b/src/plot_commands.cpp index 6fb25e4..c03f0ed 100644 --- a/src/plot_commands.cpp +++ b/src/plot_commands.cpp @@ -919,7 +919,7 @@ namespace Commands { bam1_t* a = bam_init1(); if (sam_itr_next(file_ptrs[i], region_iters[i], a) >= 0) { Segs::Align alignment = Segs::Align(a); - Segs::align_init(&alignment); + Segs::align_init(&alignment, p->opts.parse_mods); pq.push({std::move(alignment), file_ptrs[i], region_iters[i], i}); } else { bam_destroy1(a); @@ -953,7 +953,7 @@ namespace Commands { } } if (sam_itr_next(item.file_ptr, item.bam_iter, item.align.delegate) >= 0) { - Segs::align_init(&item.align); + Segs::align_init(&item.align, p->opts.parse_mods); pq.push(item); } else { bam_destroy1(item.align.delegate); diff --git a/src/plot_controls.cpp b/src/plot_controls.cpp index 7ad75e7..5e98172 100644 --- a/src/plot_controls.cpp +++ b/src/plot_controls.cpp @@ -847,7 +847,7 @@ namespace Manager { if (cl.regionIdx == regionSelection) { if (!bams.empty() && cl.regionLen >= opts.low_memory && region.end - region.start < opts.low_memory) { cl.clear(); - HGW::collectReadsAndCoverage(cl, bams[cl.bamIdx], headers[cl.bamIdx], indexes[cl.bamIdx], opts.threads, ®ion, (bool)opts.max_coverage, filters, pool); + HGW::collectReadsAndCoverage(cl, bams[cl.bamIdx], headers[cl.bamIdx], indexes[cl.bamIdx], opts.threads, ®ion, (bool)opts.max_coverage, filters, pool, opts.parse_mods); int maxY = Segs::findY(cl, cl.readQueue, opts.link_op, opts, ®ion, false); if (maxY > samMaxY) { samMaxY = maxY; diff --git a/src/plot_manager.cpp b/src/plot_manager.cpp index 59ae417..0fb0410 100644 --- a/src/plot_manager.cpp +++ b/src/plot_manager.cpp @@ -724,7 +724,7 @@ namespace Manager { } } if (reg->end - reg->start < opts.low_memory) { - HGW::collectReadsAndCoverage(collections[idx], b, hdr_ptr, index, opts.threads, reg, (bool)opts.max_coverage, filters, pool); + HGW::collectReadsAndCoverage(collections[idx], b, hdr_ptr, index, opts.threads, reg, (bool)opts.max_coverage, filters, pool, opts.parse_mods); int maxY = Segs::findY(collections[idx], collections[idx].readQueue, opts.link_op, opts, reg, false); if (maxY > samMaxY) { samMaxY = maxY; @@ -939,6 +939,7 @@ namespace Manager { } void GwPlot::drawOverlay(SkCanvas *canvas) { + if (!imageCacheQueue.empty()) { while (imageCacheQueue.front().first != frameId) { imageCacheQueue.pop_front(); diff --git a/src/segments.cpp b/src/segments.cpp index ddf1b41..d2e6fa4 100644 --- a/src/segments.cpp +++ b/src/segments.cpp @@ -253,7 +253,7 @@ namespace Segs { u, u, u, u, u, u, u, u, INV_F}; - void align_init(Align *self) { + void align_init(Align *self, const bool parse_mods) { // auto start = std::chrono::high_resolution_clock::now(); bam1_t *src = self->delegate; @@ -332,6 +332,34 @@ namespace Segs { } else { self->has_SA = false; } + bool has_mods = (bam_aux_get(self->delegate, "MM") != nullptr || bam_aux_get(self->delegate, "Mm") != nullptr); + if (has_mods && parse_mods) { + hts_base_mod_state * mod_state = hts_base_mod_state_alloc(); + int res = bam_parse_basemod(src, mod_state); + if (res >= 0) { + hts_base_mod mods[10]; + int pos = 0; + int nm = bam_next_basemod(src, mod_state, mods, 10, &pos); + while (nm > 0) { + self->any_mods.emplace_back() = ModItem(); + ModItem& mi = self->any_mods.back(); + mi.index = pos; + for (size_t m=0; m < std::min(4, nm); ++m) { + mi.mods[m] = (char)mods[m].modified_base; + mi.quals[m] = (uint8_t)mods[m].qual; + mi.strands[m] = (bool)mods[m].strand; + } +// std::cout << nm << " " << mods[0].modified_base +// << " " << mods[0].canonical_base +// << " " << mods[0].strand +// << " " << mods[0].qual +// << std::endl; + nm = bam_next_basemod(src, mod_state, mods, 10, &pos); + } +// std::cout << std::endl; + } + } + self->y = -1; // -1 has no level, -2 means initialized but filtered @@ -369,16 +397,16 @@ namespace Segs { self->any_ins.clear(); } - void init_parallel(std::vector &aligns, int n, BS::thread_pool &pool) { + void init_parallel(std::vector &aligns, int n, BS::thread_pool &pool, const bool parse_mods) { if (n == 1) { for (auto &aln : aligns) { - align_init(&aln); + align_init(&aln, parse_mods); } } else { pool.parallelize_loop(0, aligns.size(), - [&aligns](const int a, const int b) { + [&aligns, parse_mods](const int a, const int b) { for (int i = a; i < b; ++i) - align_init(&aligns[i]); + align_init(&aligns[i], parse_mods); }) .wait(); } @@ -582,6 +610,40 @@ namespace Segs { return samMaxY; } + constexpr char lookup_ref_base[256] = { + ['A'] = 1, ['a'] = 1, + ['C'] = 2, ['c'] = 2, + ['G'] = 4, ['g'] = 4, + ['T'] = 8, ['t'] = 8, + ['N'] = 15, ['n'] = 15, // All other entries default to 15 + }; + + void update_A(Mismatches& elem) { elem.A += 1; } + void update_C(Mismatches& elem) { elem.C += 1; } + void update_G(Mismatches& elem) { elem.G += 1; } + void update_T(Mismatches& elem) { elem.T += 1; } + void update_pass(Mismatches& elem) {} // For N bases + + // Lookup table for function pointers, initialized statically + void (*lookup_table_mm[16])(Mismatches&) = { + update_pass, // 0 + update_A, // 1 + update_C, // 2 + update_pass, // 3 + update_G, // 4 + update_pass, // 5 + update_pass, // 6 + update_pass, // 7 + update_T, // 8 + update_pass, // 9 + update_pass, // 10 + update_pass, // 11 + update_pass, // 12 + update_pass, // 13 + update_pass, // 14 + update_pass // 15 + }; + void findMismatches(const Themes::IniOptions &opts, ReadCollection &collection) { std::vector &mm_array = collection.mmVector; @@ -642,22 +704,23 @@ namespace Segs { for (uint32_t i = 0; i < l; ++i) { if (r_pos >= rbegin && r_pos < rend && r_pos - rbegin < mm_array_len) { char bam_base = bam_seqi(ptr_seq, idx); - switch (bam_base) { - case 1: - mm_array[r_pos - rbegin].A += 1; - break; - case 2: - mm_array[r_pos - rbegin].C += 1; - break; - case 4: - mm_array[r_pos - rbegin].G += 1; - break; - case 8: - mm_array[r_pos - rbegin].T += 1; - break; - default: - break; - } + lookup_table_mm[(size_t)bam_base](mm_array[r_pos - rbegin]); +// switch (bam_base) { +// case 1: +// mm_array[r_pos - rbegin].A += 1; +// break; +// case 2: +// mm_array[r_pos - rbegin].C += 1; +// break; +// case 4: +// mm_array[r_pos - rbegin].G += 1; +// break; +// case 8: +// mm_array[r_pos - rbegin].T += 1; +// break; +// default: +// break; +// } } idx += 1; r_pos += 1; @@ -679,48 +742,27 @@ namespace Segs { break; } - char ref_base; - switch (refSeq[r_idx]) { - case 'A': - case 'a': - ref_base = 1; - break; - case 'C': - case 'c': - ref_base = 2; - break; - case 'G': - case 'g': - ref_base = 4; - break; - case 'T': - case 't': - ref_base = 8; - break; - case 'N': - default: - ref_base = 15; - break; - } + char ref_base = lookup_ref_base[(unsigned char)refSeq[r_idx]]; char bam_base = bam_seqi(ptr_seq, idx); if (bam_base != ref_base) { - switch (bam_base) { - case 1: - mm_array[r_pos - rbegin].A += 1; - break; - case 2: - mm_array[r_pos - rbegin].C += 1; - break; - case 4: - mm_array[r_pos - rbegin].G += 1; - break; - case 8: - mm_array[r_pos - rbegin].T += 1; - break; - default: - break; - } + lookup_table_mm[(size_t)bam_base](mm_array[r_pos - rbegin]); +// switch (bam_base) { +// case 1: +// mm_array[r_pos - rbegin].A += 1; +// break; +// case 2: +// mm_array[r_pos - rbegin].C += 1; +// break; +// case 4: +// mm_array[r_pos - rbegin].G += 1; +// break; +// case 8: +// mm_array[r_pos - rbegin].T += 1; +// break; +// default: +// break; +// } } idx += 1; r_pos += 1; diff --git a/src/segments.h b/src/segments.h index 24904e7..68e2400 100644 --- a/src/segments.h +++ b/src/segments.h @@ -36,6 +36,21 @@ namespace Segs { struct EXPORT InsItem { uint32_t pos, length; }; + + struct EXPORT ModItem { // up to 4 modifications + int index; + char mods[4]; // 0 is used to indicate no more mods + uint8_t quals[4]; + bool strands[4]; + ModItem () { + index = -1; + mods[0] = 0; + mods[1] = 0; + mods[2] = 0; + mods[3] = 0; + } + }; + // // struct QueueItem { // uint32_t c_s_idx, l; @@ -62,6 +77,8 @@ namespace Segs { bool has_SA; //, initialized; std::vector block_starts, block_ends; std::vector any_ins; + std::vector any_mods; + Align(bam1_t *src) { delegate = src; } }; @@ -96,11 +113,11 @@ namespace Segs { void clear(); }; - void align_init(Align *self); // noexcept; + void align_init(Align *self, const bool parse_mods); void align_clear(Align *self); - void init_parallel(std::vector &aligns, int n, BS::thread_pool &pool); + void init_parallel(std::vector &aligns, int n, BS::thread_pool &pool, const bool parse_mods); void resetCovStartEnd(ReadCollection &cl); diff --git a/src/themes.cpp b/src/themes.cpp index c994d57..e3c2da4 100644 --- a/src/themes.cpp +++ b/src/themes.cpp @@ -378,10 +378,10 @@ namespace Themes { no_show = false; log2_cov = false; tlen_yscale = false; -// low_mem = false; expand_tracks = false; vcf_as_tracks = false; - sv_arcs=true; + sv_arcs = true; + parse_mods = true; scroll_speed = 0.15; tab_track_height = 0.05; @@ -454,6 +454,9 @@ namespace Themes { if (myIni["general"].has("sv_arcs")) { sv_arcs = myIni["general"]["sv_arcs"] == "true"; } + if (myIni["general"].has("show_mods")) { + parse_mods = myIni["general"]["show_mods"] == "true"; + } if (myIni["general"].has("session_file")) { session_file = myIni["general"]["session_file"]; } diff --git a/src/themes.h b/src/themes.h index 09f548b..ca4d763 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; - bool no_show, log2_cov, tlen_yscale, expand_tracks, vcf_as_tracks, sv_arcs; + bool no_show, log2_cov, tlen_yscale, expand_tracks, vcf_as_tracks, sv_arcs, parse_mods; float scroll_speed, tab_track_height; int scroll_right; int scroll_left; diff --git a/test/chr20:13829780-13866370_mod_call_sample.bam b/test/chr20:13829780-13866370_mod_call_sample.bam new file mode 100644 index 0000000000000000000000000000000000000000..0030e84d607e9ae2e98432cc70848d67bfe11e4e GIT binary patch literal 107862 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 C!}U)9 literal 0 HcmV?d00001 diff --git a/test/chr20:13829780-13866370_mod_call_sample.bam.bai b/test/chr20:13829780-13866370_mod_call_sample.bam.bai new file mode 100644 index 0000000000000000000000000000000000000000..1013948d8b613efaada79331ea9f80a030c76a48 GIT binary patch literal 6864 zcmeIyF$#o03ZqLYs7-(M_j8tT2A;X4tC{~y&5hZEJ@^+~-~b0WzyS_$fCC)h00%h00S<70103K0 Q2ROh14!m<<-7}x>4~Yq`kpKVy literal 0 HcmV?d00001 From b07851af4a20e4984ded1c191ce25b68da082933 Mon Sep 17 00:00:00 2001 From: kcleal Date: Thu, 4 Jul 2024 14:47:54 +0100 Subject: [PATCH 43/51] Work on UI design and mods integration --- .gw.ini | 1 + src/drawing.cpp | 447 ++++++++++++++++++++++++++++++++---------- src/drawing.h | 2 +- src/hts_funcs.cpp | 11 +- src/hts_funcs.h | 5 +- src/plot_commands.cpp | 29 ++- src/plot_manager.cpp | 189 +++++++++++------- src/segments.cpp | 78 ++++---- src/segments.h | 10 +- src/themes.cpp | 8 +- src/themes.h | 2 +- 11 files changed, 560 insertions(+), 222 deletions(-) diff --git a/.gw.ini b/.gw.ini index 480fa75..f086181 100644 --- a/.gw.ini +++ b/.gw.ini @@ -44,6 +44,7 @@ session_file= soft_clip=20000 small_indel=100000 snp=500000 +mod=250000 edge_highlights=1000000 variant_distance=100000 low_memory=1500000 diff --git a/src/drawing.cpp b/src/drawing.cpp index 4a3f9e6..34f7f4c 100644 --- a/src/drawing.cpp +++ b/src/drawing.cpp @@ -310,7 +310,8 @@ namespace Drawing { } } - inline void chooseFacecolors(int mapq, const Segs::Align &a, SkPaint &faceColor, const Themes::BaseTheme &theme) { + inline void + chooseFacecolors(int mapq, const Segs::Align &a, SkPaint &faceColor, const Themes::BaseTheme &theme) { if (mapq == 0) { switch (a.orient_pattern) { case Segs::NORMAL: @@ -356,7 +357,8 @@ namespace Drawing { } } - inline void chooseEdgeColor(int edge_type, SkPaint &edgeColor, const Themes::BaseTheme &theme) { + inline void + chooseEdgeColor(int edge_type, SkPaint &edgeColor, const Themes::BaseTheme &theme) { if (edge_type == 2) { edgeColor = theme.ecSplit; } else if (edge_type == 4) { @@ -368,19 +370,15 @@ namespace Drawing { inline void drawRectangle(SkCanvas *canvas, const float polygonH, const float yScaledOffset, const float start, - const float width, const float xScaling, - const float xOffset, const SkPaint &faceColor, SkRect &rect) { - rect.setXYWH((start * xScaling) + xOffset, yScaledOffset, width * xScaling, polygonH); + const float width, const SkPaint &faceColor, SkRect &rect) { + rect.setXYWH(start, yScaledOffset, width, polygonH); canvas->drawRect(rect, faceColor); } inline void drawLeftPointedRectangle(SkCanvas *canvas, const float polygonH, const float yScaledOffset, float start, - float width, - const float xScaling, const float maxX, const float xOffset, const SkPaint &faceColor, + float width, const float maxX, const float xOffset, const SkPaint &faceColor, SkPath &path, const float slop, bool edged, SkPaint &edgeColor) { - start *= xScaling; - width *= xScaling; path.reset(); path.moveTo(start + xOffset, yScaledOffset); path.lineTo(start - slop + xOffset, yScaledOffset + (polygonH * 0.5)); @@ -396,13 +394,10 @@ namespace Drawing { inline void drawRightPointedRectangle(SkCanvas *canvas, const float polygonH, const float yScaledOffset, float start, - float width, - const float xScaling, const float maxX, const float xOffset, const SkPaint &faceColor, + float width, const float maxX, const float xOffset, const SkPaint &faceColor, SkPath &path, const float slop, bool edged, SkPaint &edgeColor) { - start *= xScaling; - width *= xScaling; path.reset(); path.moveTo(start + xOffset, yScaledOffset); path.lineTo(start + xOffset, yScaledOffset + polygonH); @@ -424,32 +419,46 @@ namespace Drawing { canvas->drawPath(path, lc); } - void drawIns(SkCanvas *canvas, float y0, float start, float yScaling, float xOffset, + inline void + drawIns(SkCanvas *canvas, float y0, float start, float yScaling, float xOffset, float yOffset, float textW, const SkPaint &sidesColor, const SkPaint &faceColor, SkPath &path, - SkRect &rect, float pH, float pH_05, float pH_95) { + SkRect &rect, float pH, float overhang) { float x = start + xOffset; float y = y0 * yScaling; - float overhang = textW * 0.125; float text_half = textW * 0.5; - rect.setXYWH(x - text_half - 2, y + yOffset, textW + 2, pH); + float box_left = x - text_half - overhang; + float box_w = textW + overhang; + rect.setXYWH(box_left, y + yOffset, textW + overhang, pH); canvas->drawRect(rect, faceColor); - path.reset(); - path.moveTo(x - text_half - overhang, yOffset + y + pH_05); - path.lineTo(x + text_half + overhang, yOffset + y + pH_05); - path.moveTo(x - text_half - overhang, yOffset + y + pH_95); - path.lineTo(x + text_half + overhang, yOffset + y + pH_95); - path.moveTo(x, yOffset + y); - path.lineTo(x, yOffset + y + pH); - canvas->drawPath(path, sidesColor); + + rect.setXYWH(box_left - overhang, yOffset + y, box_w + overhang + overhang, overhang);//pH * 0.2); + canvas->drawRect(rect, faceColor); + + rect.setXYWH(box_left - overhang, yOffset + y + pH - overhang, box_w + overhang + overhang, overhang);//pH * 0.2); + canvas->drawRect(rect, faceColor); +// path.reset(); +// path.moveTo(x - text_half - overhang, yOffset + y + pH_05); +// path.lineTo(x + text_half + overhang, yOffset + y + pH_05); +// path.moveTo(x - text_half - overhang, yOffset + y + pH_95); +// path.lineTo(x + text_half + overhang, yOffset + y + pH_95); +// path.moveTo(x, yOffset + y); +// path.lineTo(x, yOffset + y + pH); +// canvas->drawPath(path, sidesColor); } - constexpr char lookup_ref_base[256] = { - ['A'] = 1, ['a'] = 1, - ['C'] = 2, ['c'] = 2, - ['G'] = 4, ['g'] = 4, - ['T'] = 8, ['t'] = 8, - ['N'] = 15, ['n'] = 15, // All other entries default to 15 - }; + constexpr std::array make_lookup_ref_base() { + std::array a{}; + for (auto& elem : a) { + elem = 15; // Initialize all elements to 15 + } + a['A'] = 1; a['a'] = 1; + a['C'] = 2; a['c'] = 2; + a['G'] = 4; a['g'] = 4; + a['T'] = 8; a['t'] = 8; + a['N'] = 15; a['n'] = 15; + return a; + } + constexpr std::array lookup_ref_base = make_lookup_ref_base(); void update_A(Segs::Mismatches& elem) { elem.A += 1; } void update_C(Segs::Mismatches& elem) { elem.C += 1; } @@ -482,6 +491,67 @@ namespace Drawing { float width, float xScaling, float xOffset, float mmPosOffset, float yScaledOffset, float pH, int l_qseq, std::vector &mm_array, bool &collection_processed) { + if (!region->refSeq || align.blocks.empty()) { + return; + } + if (mm_array.empty()) { + collection_processed = true; + return; + } + uint8_t *ptr_seq = bam_get_seq(align.delegate); + if (ptr_seq == nullptr) { + return; + } + uint8_t *ptr_qual = bam_get_qual(align.delegate); + + const char *refSeq = region->refSeq; + + float precalculated_xOffset_mmPosOffset = xOffset + mmPosOffset; + + for (const auto& blk : align.blocks) { + uint32_t idx_start, idx_end; + uint32_t pos_start; + if ((int)blk.end < region->start) { + continue; + } else if ((int)blk.start >= region->end) { + return; + } + if ((int)blk.start < region->start) { + pos_start = region->start; + idx_start = blk.seq_index + (pos_start - blk.start); + } else { + pos_start = blk.start; + idx_start = blk.seq_index; + } + if ((int)blk.end < region->end) { + idx_end = blk.seq_index + (blk.end - blk.start); + } else { + idx_end = (blk.seq_index + (blk.end - blk.start)) - (blk.end - region->end); + } + size_t ref_idx = pos_start - region->start; + + for (size_t i=idx_start; i < (size_t)idx_end; ++i) { + char ref_base = lookup_ref_base[(unsigned char)refSeq[ref_idx]]; + char bam_base = bam_seqi(ptr_seq, i); + if (bam_base != ref_base) { + float p = ref_idx * xScaling; + uint32_t colorIdx = (l_qseq == 0) ? 10 : (ptr_qual[i] > 10) ? 10 : ptr_qual[i]; + rect.setXYWH(p + precalculated_xOffset_mmPosOffset, yScaledOffset, width, pH); + canvas->drawRect(rect, theme.BasePaints[bam_base][colorIdx]); + if (!collection_processed) { + lookup_table_mm[(unsigned char)bam_base](mm_array[ref_idx]); + } + } + ref_idx += 1; + } + } + } + + void drawMismatchesNoMD2(SkCanvas *canvas, SkRect &rect, const Themes::BaseTheme &theme, const Utils::Region *region, + const Segs::Align &align, + float width, float xScaling, float xOffset, float mmPosOffset, float yScaledOffset, + float pH, int l_qseq, std::vector &mm_array, + bool &collection_processed) { if (mm_array.empty()) { collection_processed = true; return; @@ -506,6 +576,7 @@ namespace Drawing { const uint32_t rend = region->end; uint32_t idx = 0, op, l; float p, precalculated_xOffset_mmPosOffset = xOffset + mmPosOffset; // Precalculate this sum + for (uint32_t k = 0; k < cigar_l; k++) { op = cigar_p[k] & BAM_CIGAR_MASK; l = cigar_p[k] >> BAM_CIGAR_SHIFT; @@ -621,24 +692,23 @@ namespace Drawing { } void drawBlock(bool plotPointedPolygons, bool pointLeft, bool edged, float s, float e, float width, - float pointSlop, float pH, float yScaledOffset, float xScaling, float xOffset, float regionPixels, + float pointSlop, float pH, float yScaledOffset, float xOffset, float regionPixels, size_t idx, size_t nBlocks, int regionLen, const Segs::Align &a, SkCanvas *canvas, SkPath &path, SkRect &rect, SkPaint &faceColor, SkPaint &edgeColor) { if (plotPointedPolygons) { if (pointLeft) { - drawLeftPointedRectangle(canvas, pH, yScaledOffset, s, width, xScaling, + drawLeftPointedRectangle(canvas, pH, yScaledOffset, s, width, regionPixels, xOffset, faceColor, path, pointSlop, edged, edgeColor); } else { - drawRightPointedRectangle(canvas, pH, yScaledOffset, s, width, xScaling, + drawRightPointedRectangle(canvas, pH, yScaledOffset, s, width, regionPixels, xOffset, faceColor, path, pointSlop, edged, edgeColor); } } else { - drawRectangle(canvas, pH, yScaledOffset, s, width, xScaling, xOffset, faceColor, - rect); + drawRectangle(canvas, pH, yScaledOffset, s + xOffset, width, faceColor,rect); } } @@ -652,7 +722,6 @@ namespace Drawing { int lastEnd = lastEndi - regionBegin; starti -= regionBegin; - lastEnd = (lastEnd < 0) ? 0 : lastEnd; int size = starti - lastEnd; if (size <= 0) { return; @@ -697,9 +766,72 @@ namespace Drawing { } } + void drawMods(SkCanvas *canvas, SkRect &rect, const Themes::BaseTheme &theme, const Utils::Region *region, + const Segs::Align &align, + float width, float xScaling, float xOffset, float mmPosOffset, float yScaledOffset, + float pH, int l_qseq, float monitorScale) { + if (align.any_mods.empty()) { + return; + } + float precalculated_xOffset_mmPosOffset = xOffset + mmPosOffset + (0.5 * xScaling) - 2; + + auto mod_it = align.any_mods.begin(); + auto mod_end = align.any_mods.end(); + + SkPaint painth; + painth.setARGB(127, 52, 255, 96); + painth.setStrokeWidth(std::fmin(pH / 3, 4 * monitorScale)); + painth.setAntiAlias(true); + painth.setStrokeCap(SkPaint::kRound_Cap); + + SkPaint paintm; + paintm.setARGB(127, 252, 186, 3); + paintm.setStrokeWidth(std::fmin(pH / 3, 4 * monitorScale)); + paintm.setAntiAlias(true); + paintm.setStrokeCap(SkPaint::kRound_Cap); + + float top = yScaledOffset + (pH / 3); + float bottom = yScaledOffset + pH - (pH / 3); + + for (const auto& blk : align.blocks) { + if ((int)blk.end < region->start) { + continue; + } else if ((int)blk.start >= region->end) { + return; + } + int idx_start = blk.seq_index; + int idx_end = blk.seq_index + (blk.end - blk.start); + while (mod_it != mod_end && mod_it->index < idx_start) { + ++mod_it; + } + while (mod_it != mod_end && mod_it->index < idx_end) { + float x = (((blk.start + mod_it->index - idx_start) - region->start) * xScaling) + precalculated_xOffset_mmPosOffset; + for (size_t j=0; j < mod_it->n_mods; ++j) { + switch (mod_it->mods[j]) { + case 'm': // 5mC + paintm.setAlpha(mod_it->quals[j]); + canvas->drawPoint(x, top, paintm); + break; + case 'h': // 5hmC + painth.setAlpha(mod_it->quals[j]); + canvas->drawPoint(x, bottom, painth); + break; + default: + // todo other + + break; + } + } + ++mod_it; + } + + } + + } + void drawCollection(const Themes::IniOptions &opts, Segs::ReadCollection &cl, SkCanvas *canvas, float trackY, float yScaling, const Themes::Fonts &fonts, int linkOp, - float refSpace, float pointSlop, float textDrop, float pH) { + float refSpace, float pointSlop, float textDrop, float pH, float monitorScale) { SkPaint faceColor; SkPaint edgeColor; @@ -719,7 +851,9 @@ namespace Drawing { float xScaling = cl.xScaling; float xOffset = cl.xOffset; float yOffset = cl.yOffset; - float regionPixels = cl.regionPixels; //regionLen * xScaling; + float regionPixels = cl.regionPixels; + + int min_gap_size = 1 + (1 / (cl.regionPixels / regionLen)); bool plotSoftClipAsBlock = cl.plotSoftClipAsBlock; //regionLen > opts.soft_clip_threshold; bool plotPointedPolygons = cl.plotPointedPolygons; // regionLen < 50000; @@ -729,14 +863,12 @@ namespace Drawing { cl.skipDrawingReads = true; - float pH_05 = pH * 0.05; - float pH_95 = pH * 0.95; for (const auto &a: cl.readQueue) { int Y = a.y; if (Y < 0) { continue; } - bool indelTextFits = fonts.overlayHeight * 0.7 < yScaling; + bool indelTextFits = fonts.overlayHeight < yScaling; //fonts.overlayHeight * 0.7 < yScaling; int mapq = a.delegate->core.qual; float yScaledOffset = (Y * yScaling) + yOffset; chooseFacecolors(mapq, a, faceColor, theme); @@ -746,7 +878,7 @@ namespace Drawing { } else { pointLeft = false; } - size_t nBlocks = a.block_starts.size(); + size_t nBlocks = a.blocks.size(); if (drawEdges && a.edge_type != 1) { edged = true; chooseEdgeColor(a.edge_type, edgeColor, theme); @@ -755,46 +887,157 @@ namespace Drawing { } double width, s, e, textW; int lastEnd = 1215752191; - int starti; - bool line_only; - for (size_t idx = 0; idx < nBlocks; ++idx) { - starti = (int) a.block_starts[idx]; - if (idx > 0) { - lastEnd = (int) a.block_ends[idx - 1]; - } - - if (starti > regionEnd) { - if (lastEnd < regionEnd) { - line_only = true; - } else { - break; + int starti = 0; +// bool line_only; + + size_t idx; + // draw gapped + if (nBlocks > 1) { + idx = 1; + size_t idx_begin = 0; + for (; idx < nBlocks; ++idx) { + starti = (int) a.blocks[idx].start; + lastEnd = (int) a.blocks[idx - 1].end; + if (starti - lastEnd == 0) { + continue; // insertion + } + if (starti - lastEnd >= min_gap_size) { + s = (double)a.blocks[idx_begin].start - regionBegin; + e = (double)a.blocks[idx - 1].end - regionBegin; + width = (e - s) * xScaling; + drawBlock(plotPointedPolygons, pointLeft, edged, (float) s * xScaling, (float) e, (float) width, + pointSlop, pH, yScaledOffset, xOffset, regionPixels, idx, nBlocks, regionLen, + a, canvas, path, rect, faceColor, edgeColor); + idx_begin = idx; } - } else { - line_only = false; } - - e = (double) a.block_ends[idx]; - if (e < regionBegin) { continue; } - s = starti - regionBegin; - e -= regionBegin; - s = (s < 0) ? 0 : s; - e = (e > regionLen) ? regionLen : e; - width = e - s; - if (!line_only) { - drawBlock(plotPointedPolygons, pointLeft, edged, (float) s, (float) e, (float) width, - pointSlop, pH, yScaledOffset, xScaling, xOffset, regionPixels, - idx, nBlocks, regionLen, - a, canvas, path, rect, faceColor, edgeColor); + s = (double)a.blocks[idx_begin].start - regionBegin; + e = (double)a.blocks[idx - 1].end - regionBegin; + width = (e - s) * xScaling; + drawBlock(plotPointedPolygons, pointLeft, edged, (float) s * xScaling, (float) e, (float) width, + pointSlop, pH, yScaledOffset, xOffset, regionPixels, idx, nBlocks, regionLen, + a, canvas, path, rect, faceColor, edgeColor); + idx_begin = idx; + + idx = 1; + idx_begin = 0; + for (; idx < nBlocks; ++idx) { + starti = (int) a.blocks[idx].start; + lastEnd = (int) a.blocks[idx - 1].end; + if (starti - lastEnd == 0) { + continue; // insertion + } + if (lastEnd <= regionEnd && regionBegin <= starti) { + drawDeletionLine(a, canvas, path, opts, fonts, + regionBegin, idx, Y, regionLen, starti, lastEnd, + regionPixels, xScaling, yScaling, xOffset, yOffset, + textDrop, text_del, indelTextFits); + } } - // add lines and text between gaps - if (idx > 0) { - drawDeletionLine(a, canvas, path, opts, fonts, - regionBegin, idx, Y, regionLen, starti, lastEnd, - regionPixels, xScaling, yScaling, xOffset, yOffset, - textDrop, text_del, indelTextFits); - } + } else { + 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, idx, nBlocks, regionLen, + a, canvas, path, rect, faceColor, edgeColor); } + + // draw blocks +// idx = 0; +// size_t from = 0; +// for (; idx < nBlocks; ++idx) { +// if (idx > 0 && (a.blocks[idx].start - a.blocks[idx - 1].end < min_block_width)) { +// continue; +// } +// +// if (a.blocks[idx].end - a.blocks[idx].start < min_block_width) { +// continue; +// } +// s = (double)a.blocks[from].start - regionBegin; +// e = (double)a.blocks[idx].end - regionBegin; +// +// s = (s < 0) ? 0 : s; +// e = (e > regionLen) ? regionLen : e; +// width = (e - s) * xScaling; +// +// drawBlock(plotPointedPolygons, pointLeft, edged, (float) s * xScaling, (float) e, (float) width, +// pointSlop, pH, yScaledOffset, xOffset, regionPixels, idx, nBlocks, regionLen, +// a, canvas, path, rect, faceColor, edgeColor); +// +// from = idx; +// } +// if (from < nBlocks-1) { +// s = (double)a.blocks[from].start - regionBegin; +// e = (double)a.blocks.back().end - regionBegin; +// s = (s < 0) ? 0 : s; +// e = (e > regionLen) ? regionLen : e; +// width = (e - s) * xScaling; +// drawBlock(plotPointedPolygons, pointLeft, edged, (float) s * xScaling, (float) e, (float) width, +// pointSlop, pH, yScaledOffset, xOffset, regionPixels, idx, nBlocks, regionLen, +// a, canvas, path, rect, faceColor, edgeColor); +// } + + +// +// +// size_t extend_from = 0; +// +// idx = 0; +// for (; idx < nBlocks; ++idx) { +// +// // add lines and text between gaps +//// if (idx > 0 && lastEnd != starti) { +//// drawDeletionLine(a, canvas, path, opts, fonts, +//// regionBegin, idx, Y, regionLen, starti, lastEnd, +//// regionPixels, xScaling, yScaling, xOffset, yOffset, +//// textDrop, text_del, indelTextFits); +//// } +// +// +// +// starti = (int) a.blocks[idx].start; +// +// if (idx > 0) { +// lastEnd = (int) a.blocks[idx - 1].end; +// } +// +// if (starti > regionEnd) { +// break; +// } +// +// if (starti > regionEnd) { +// if (lastEnd < regionEnd) { +// line_only = true; +// } else { +// break; +// } +// } else { +// line_only = false; +// } +// +// e = (double) a.blocks[idx].end; +// +// if (e < regionBegin) { continue; } +// s = starti - regionBegin; +// e -= regionBegin; +// s = (s < 0) ? 0 : s; +// e = (e > regionLen) ? regionLen : e; +// width = (e - s) * xScaling; +// +// if (!line_only && width > min_block_width) { +// +// +// drawBlock(plotPointedPolygons, pointLeft, edged, (float) s * xScaling, (float) e, (float) width, +// pointSlop, pH, yScaledOffset, xOffset, regionPixels, idx, nBlocks, regionLen, +// a, canvas, path, rect, faceColor, edgeColor); +// +// +// +// } +// +// } // add soft-clip blocks int start = (int) a.pos - regionBegin; int end = (int) a.reference_end - regionBegin; @@ -813,12 +1056,12 @@ namespace Drawing { } if (e > 0 && s < regionLen && width > 0) { if (pointLeft && plotPointedPolygons) { - drawLeftPointedRectangle(canvas, pH, yScaledOffset, s, width, xScaling, + drawLeftPointedRectangle(canvas, pH, yScaledOffset, s * xScaling, width * xScaling, regionPixels, xOffset, (mapq == 0) ? theme.fcSoftClip0 : theme.fcSoftClip, path, pointSlop, false, edgeColor); } else { - drawRectangle(canvas, pH, yScaledOffset, s, width, xScaling, xOffset, + drawRectangle(canvas, pH, yScaledOffset, (s * xScaling) + xOffset, width * xScaling, (mapq == 0) ? theme.fcSoftClip0 : theme.fcSoftClip, rect); } } @@ -842,13 +1085,13 @@ namespace Drawing { } if (s < regionLen && e > 0) { if (!pointLeft && plotPointedPolygons) { - drawRightPointedRectangle(canvas, pH, yScaledOffset, s, width, xScaling, + drawRightPointedRectangle(canvas, pH, yScaledOffset, s * xScaling, width * xScaling, regionPixels, xOffset, (mapq == 0) ? theme.fcSoftClip0 : theme.fcSoftClip, path, pointSlop, false, edgeColor); } else { - drawRectangle(canvas, pH, yScaledOffset, s, width, xScaling, - xOffset, (mapq == 0) ? theme.fcSoftClip0 : theme.fcSoftClip, rect); + drawRectangle(canvas, pH, yScaledOffset, (s * xScaling) + xOffset, width * xScaling, + (mapq == 0) ? theme.fcSoftClip0 : theme.fcSoftClip, rect); } } } @@ -865,27 +1108,27 @@ namespace Drawing { if (ins.length > (uint32_t) opts.indel_length) { if (regionLen < 500000 && indelTextFits) { // line and text drawIns(canvas, Y, p, yScaling, xOffset, yOffset, textW, theme.insS, - theme.fcIns, path, rect, pH, pH_05, pH_95); + theme.fcIns, path, rect, pH, monitorScale); text_ins.emplace_back() = {SkTextBlob::MakeFromString(indelChars, fonts.overlay), (float)(p - (textW * 0.5) + xOffset - 2), ((Y + polygonHeight) * yScaling) + yOffset - textDrop}; } else { // line only drawIns(canvas, Y, p, yScaling, xOffset, yOffset, xScaling, theme.insS, - theme.fcIns, path, rect, pH, pH_05, pH_95); + theme.fcIns, path, rect, pH, monitorScale); } } else if (regionLen < 100000 && regionLen < opts.small_indel_threshold) { // line only drawIns(canvas, Y, p, yScaling, xOffset, yOffset, xScaling, theme.insS, - theme.fcIns, path, rect, pH, pH_05, pH_95); + theme.fcIns, path, rect, pH, monitorScale); } } } } // add mismatches - if (regionLen > opts.snp_threshold && plotSoftClipAsBlock) { - continue; - } +// if (regionLen > opts.snp_threshold && plotSoftClipAsBlock) { +// continue; +// } if (l_seq == 0) { continue; } @@ -907,6 +1150,10 @@ namespace Drawing { drawMismatchesNoMD(canvas, rect, theme, cl.region, a, (float) width, xScaling, xOffset, mmPosOffset, yScaledOffset, pH, l_qseq, mm_vector, cl.collection_processed); } + if (opts.parse_mods && regionLen <= opts.mod_threshold) { + drawMods(canvas, rect, theme, cl.region, a, (float) width, xScaling, xOffset, mmPosOffset, + yScaledOffset, pH, l_qseq, monitorScale); + } // add soft-clips if (!plotSoftClipAsBlock) { @@ -918,15 +1165,11 @@ namespace Drawing { int opLen = (int) a.right_soft_clip; for (int idx = l_seq - opLen; idx < l_seq; ++idx) { float p = pos * xScaling; -// if (0 <= p && p < regionPixels) { uint8_t base = bam_seqi(ptr_seq, idx); uint8_t qual = ptr_qual[idx]; colorIdx = (l_qseq == 0) ? 10 : (qual > 10) ? 10 : qual; rect.setXYWH(p + xOffset + mmPosOffset, yScaledOffset, xScaling * mmScaling, pH); canvas->drawRect(rect, theme.BasePaints[base][colorIdx]); -// } else if (p > regionPixels) { -// break; -// } pos += 1; } } @@ -936,15 +1179,11 @@ namespace Drawing { int pos = (int) a.pos - regionBegin - opLen; for (int idx = 0; idx < opLen; ++idx) { float p = pos * xScaling; -// if (0 <= p && p < regionPixels) { uint8_t base = bam_seqi(ptr_seq, idx); uint8_t qual = ptr_qual[idx]; colorIdx = (l_qseq == 0) ? 10 : (qual > 10) ? 10 : qual; rect.setXYWH(p + xOffset + mmPosOffset, yScaledOffset, xScaling * mmScaling, pH); canvas->drawRect(rect, theme.BasePaints[base][colorIdx]); -// } else if (p >= regionPixels) { -// break; -// } pos += 1; } } @@ -978,12 +1217,16 @@ namespace Drawing { const Segs::Align *segA = ind[jdx]; const Segs::Align *segB = ind[jdx + 1]; - if (segA->y == -1 || segB->y == -1 || segA->block_ends.empty() || - segB->block_ends.empty() || +// if (segA->y == -1 || segB->y == -1 || segA->block_ends.empty() || + if (segA->y == -1 || segB->y == -1 || segA->blocks.empty() || + segB->blocks.empty() || +// segB->block_ends.empty() || (segA->delegate->core.tid != segB->delegate->core.tid)) { continue; } - long cstart = std::min(segA->block_ends.front(), segB->block_ends.front()); - long cend = std::max(segA->block_starts.back(), segB->block_starts.back()); +// long cstart = std::min(segA->block_ends.front(), segB->block_ends.front()); + long cstart = std::min(segA->blocks.front().end, segB->blocks.front().end); +// long cend = std::max(segA->block_starts.back(), segB->block_starts.back()); + long cend = std::max(segA->blocks.back().start, segB->blocks.back().start); double x_a = ((double) cstart - (double) cl.region->start) * cl.xScaling; double x_b = ((double) cend - (double) cl.region->start) * cl.xScaling; diff --git a/src/drawing.h b/src/drawing.h index a46a81f..798c9b5 100644 --- a/src/drawing.h +++ b/src/drawing.h @@ -33,7 +33,7 @@ namespace Drawing { SkCanvas *canvas, const Themes::Fonts &fonts, float covY, float refSpace); void drawCollection(const Themes::IniOptions &opts, Segs::ReadCollection &cl, SkCanvas* canvas, - float trackY, float yScaling, const Themes::Fonts &fonts, int linkOp, float refSpace, float pointSlop, float textDrop, float pH); + float trackY, float yScaling, const Themes::Fonts &fonts, int linkOp, float refSpace, float pointSlop, float textDrop, float pH, float monitorScale); void drawRef(const Themes::IniOptions &opts, std::vector ®ions, int fb_width, SkCanvas *canvas, const Themes::Fonts &fonts, float refSpace, float nRegions, float gap); diff --git a/src/hts_funcs.cpp b/src/hts_funcs.cpp index b25c683..c56e38e 100644 --- a/src/hts_funcs.cpp +++ b/src/hts_funcs.cpp @@ -348,7 +348,8 @@ namespace HGW { BS::thread_pool &pool, float pointSlop, float textDrop, - float pH) { + float pH, + float monitorScale) { const int BATCH = 1500; bam1_t *src; hts_itr_t *iter_q; @@ -393,7 +394,7 @@ namespace HGW { } } Segs::findY(col, readQueue, opts.link_op, opts, region, false); - Drawing::drawCollection(opts, col, canvas, trackY, yScaling, fonts, opts.link_op, refSpace, pointSlop, textDrop, pH); + Drawing::drawCollection(opts, col, canvas, trackY, yScaling, fonts, opts.link_op, refSpace, pointSlop, textDrop, pH, monitorScale); for (int i=0; i < BATCH; ++ i) { Segs::align_clear(&readQueue[i]); @@ -417,7 +418,7 @@ namespace HGW { } } Segs::findY(col, readQueue, opts.link_op, opts, region, false); - Drawing::drawCollection(opts, col, canvas, trackY, yScaling, fonts, opts.link_op, refSpace, pointSlop, textDrop, pH); + Drawing::drawCollection(opts, col, canvas, trackY, yScaling, fonts, opts.link_op, refSpace, pointSlop, textDrop, pH, monitorScale); for (int i=0; i < BATCH; ++ i) { Segs::align_clear(&readQueue[i]); } @@ -430,7 +431,7 @@ namespace HGW { bool coverage, std::vector &filters, Themes::IniOptions &opts, SkCanvas *canvas, float trackY, float yScaling, Themes::Fonts &fonts, float refSpace, - float pointSlop, float textDrop, float pH) { + float pointSlop, float textDrop, float pH, float monitorScale) { // if (region->end == 0) { // return; // } @@ -469,7 +470,7 @@ namespace HGW { Segs::addToCovArray(col.covArr, readQueue.back(), region->start, region->end, l_arr); } Segs::findY(col, readQueue, opts.link_op, opts, region, false); - Drawing::drawCollection(opts, col, canvas, trackY, yScaling, fonts, opts.link_op, refSpace, pointSlop, textDrop, pH); + Drawing::drawCollection(opts, col, canvas, trackY, yScaling, fonts, opts.link_op, refSpace, pointSlop, textDrop, pH, monitorScale); Segs::align_clear(&readQueue.back()); } } diff --git a/src/hts_funcs.h b/src/hts_funcs.h index 08aa9a5..c758de6 100644 --- a/src/hts_funcs.h +++ b/src/hts_funcs.h @@ -111,13 +111,14 @@ namespace HGW { BS::thread_pool &pool, float pointSlop, float textDrop, - float pH); + float pH, + float monitorScale); void iterDraw(Segs::ReadCollection &col, htsFile *b, sam_hdr_t *hdr_ptr, hts_idx_t *index, Utils::Region *region, bool coverage, std::vector &filters, Themes::IniOptions &opts, SkCanvas *canvas, float trackY, float yScaling, Themes::Fonts &fonts, float refSpace, - float pointSlop, float textDrop, float pH); + float pointSlop, float textDrop, float pH, float monitorScale); void trimToRegion(Segs::ReadCollection &col, bool coverage, int snp_threshold); diff --git a/src/plot_commands.cpp b/src/plot_commands.cpp index c03f0ed..bf40910 100644 --- a/src/plot_commands.cpp +++ b/src/plot_commands.cpp @@ -230,6 +230,22 @@ namespace Commands { return Err::NONE; } + Err mods(Plot* p) { + p->opts.mod_threshold = (p->opts.mod_threshold == 0) ? std::stoi(p->opts.myIni["view_thresholds"]["mod"]) : 0; + if (p->mode == Manager::Show::SINGLE) { + p->processed = true; + for (auto &cl : p->collections) { + cl.skipDrawingReads = false; + cl.skipDrawingCoverage = false; + } + } else { + p->processed = false; + } + p->redraw = true; + p->imageCache.clear(); + return Err::NONE; + } + Err edges(Plot* p) { p->opts.edge_highlights = (p->opts.edge_highlights == 0) ? std::stoi(p->opts.myIni["view_thresholds"]["edge_highlights"]) : 0; if (p->mode == Manager::Show::SINGLE) { @@ -728,6 +744,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()); new_regions.push_back(dummy_region); p->fetchRefSeq(new_regions.back()); } @@ -1042,6 +1059,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()); } else { if (p->regions[p->regionSelection].chrom == rgn.chrom) { rgn.markerPos = p->regions[p->regionSelection].markerPos; @@ -1049,6 +1067,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()); } } else { // search all tracks for matching name, slow but ok for small tracks if (!p->tracks.empty()) { @@ -1253,7 +1272,6 @@ namespace Commands { void run_command_map(Plot* p, std::string& command, std::ostream& out) { std::vector parts = Utils::split(command, ' '); - static std::unordered_map&, std::ostream& out)>> functionMap = { @@ -1269,8 +1287,9 @@ namespace Commands { {"insertions", PARAMS { return insertions(p); }}, {"mm", PARAMS { return mismatches(p); }}, {"mismatches", PARAMS { return mismatches(p); }}, + {"mods", PARAMS { return mods(p); }}, {"edges", PARAMS { return edges(p); }}, - {"soft_clips", PARAMS { return soft_clips(p); }}, + {"soft-clips", PARAMS { return soft_clips(p); }}, {"log2-cov", PARAMS { return log2_cov(p); }}, {"expand-tracks", PARAMS { return expand_tracks(p); }}, {"tlen-y", PARAMS { return tlen_y(p); }}, @@ -1320,5 +1339,11 @@ namespace Commands { } save_command_or_handle_err(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_manager.cpp b/src/plot_manager.cpp index 84edd85..26b274e 100644 --- a/src/plot_manager.cpp +++ b/src/plot_manager.cpp @@ -188,6 +188,7 @@ namespace Manager { std::terminate(); } + // GLFWmonitor* primary = glfwGetPrimaryMonitor(); // const GLFWvidmode* mode = glfwGetVideoMode(primary); // @@ -220,7 +221,6 @@ namespace Manager { 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); @@ -871,7 +871,7 @@ namespace Manager { for (auto &cl: collections) { canvasR->save(); - + std::cout << cl.skipDrawingCoverage << " " << cl.skipDrawingReads << std::endl; // Copy some of the image from the last frame if ((cl.skipDrawingCoverage || cl.skipDrawingReads) && !imageCacheQueue.empty()) { if (cl.skipDrawingCoverage) { @@ -892,24 +892,33 @@ namespace Manager { } canvasR->drawPaint(opts.theme.bgPaint); - if (cl.regionLen >= opts.low_memory && !bams.empty()) { // low memory mode will be used - cl.clear(); -// HGW::iterDraw(cl, bams[cl.bamIdx], headers[cl.bamIdx], indexes[cl.bamIdx], ®ions[cl.regionIdx], (bool) opts.max_coverage, -// filters, opts, canvasR, trackY, yScaling, fonts, refSpace, pointSlop, textDrop, pH); - 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, canvasR, trackY, yScaling, fonts, refSpace, pointSlop, textDrop, pH); + if (!cl.skipDrawingReads) { + + + if (cl.regionLen >= opts.low_memory && !bams.empty()) { // low memory mode will be used + cl.clear(); + std::cout << " iter draw\n"; + 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, canvasR, 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, canvasR, trackY, yScaling, fonts, refSpace, pool, + 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, canvasR, trackY, yScaling, fonts, refSpace, pool, pointSlop, textDrop, pH); + std::cout << " full draw\n"; + Drawing::drawCollection(opts, cl, canvasR, trackY, yScaling, fonts, opts.link_op, refSpace, + pointSlop, textDrop, pH, monitorScale); } - } else { - Drawing::drawCollection(opts, cl, canvasR, trackY, yScaling, fonts, opts.link_op, refSpace, pointSlop, textDrop, pH); } - canvasR->restore(); } + std::cout << " done draw\n"; if (opts.max_coverage) { Drawing::drawCoverage(opts, collections, canvasR, fonts, covY, refSpace); } @@ -968,7 +977,7 @@ namespace Manager { SkRect rect; SkPaint rect_paint = opts.theme.bgPaint; rect_paint.setAlpha(160); - float xbox = std::fmin(xPos_fb + monitorScale, drawWidth); + float xbox = xPos_fb + monitorScale; rect.setXYWH(xbox, fb_height - (yh *2) + (monitorScale), estimatedTextWidth, (yh*(float)1.33) - monitorScale - monitorScale); canvas->drawRect(rect, rect_paint); sk_sp blob = SkTextBlob::MakeFromString(s.c_str(), fonts.overlay); @@ -1050,50 +1059,7 @@ namespace Manager { xposm *= (float) fb_width / (float) windowW; yposm *= (float) fb_height / (float) windowH; } - float half_h = (float)fb_height / 2; - bool tool_popup = (xposm > 0 && xposm <= 60 && yposm >= half_h - 150 && yposm <= half_h + 150 && (xDrag == -1000000 || xDrag == 0) && (yDrag == -1000000 || yDrag == 0)); - if (tool_popup) { - SkRect rect{}; - SkPaint pop_paint = opts.theme.bgPaint; - pop_paint.setStrokeWidth(monitorScale); - pop_paint.setStyle(SkPaint::kStrokeAndFill_Style); - pop_paint.setAntiAlias(true); - pop_paint.setAlpha(255); - rect.setXYWH(-10, half_h - 35 * monitorScale, 30 * monitorScale + 10, 70 * monitorScale); - canvas->drawRoundRect(rect, 5 * monitorScale, 5 * monitorScale, pop_paint); - - SkPaint cog_paint = opts.theme.lcBright; - cog_paint.setAntiAlias(true); - cog_paint.setStrokeWidth(3 * monitorScale); - cog_paint.setStyle(SkPaint::kStrokeAndFill_Style); - canvas->drawCircle(15 * monitorScale, half_h - 12.5 * monitorScale, 5 * monitorScale, cog_paint); - - SkPath path; - path.moveTo(15 * monitorScale, half_h - (22.5 * monitorScale)); - path.lineTo(15 * monitorScale, half_h - (2.5 * monitorScale)); - canvas->drawPath(path, cog_paint); - path.moveTo(5 * monitorScale, half_h - 12.5 * monitorScale); - path.lineTo((25 * monitorScale), half_h - 12.5 * monitorScale); - canvas->drawPath(path, cog_paint); - path.moveTo(8 * monitorScale, half_h - (19.5 * monitorScale)); - path.lineTo(22 * monitorScale, half_h - (5.5 * monitorScale)); - canvas->drawPath(path, cog_paint); - path.moveTo(8 * monitorScale, half_h - (5.5 * monitorScale)); - path.lineTo(22 * monitorScale, half_h - (19.5 * monitorScale)); - canvas->drawPath(path, cog_paint); - canvas->drawCircle(15 * monitorScale, half_h - 12.5 * monitorScale, 2.5 * monitorScale, pop_paint); - rect.setXYWH(7.5 * monitorScale, half_h + 7.5 * monitorScale, 15 * monitorScale, 15 * monitorScale); - cog_paint.setStrokeWidth(monitorScale); - cog_paint.setStyle(SkPaint::kStroke_Style); - canvas->drawRoundRect(rect, 3.5 * monitorScale, 3.5 * monitorScale, cog_paint); - - } else if (drawLine && mode != SETTINGS) { - SkPath path; - path.moveTo(xposm, 0); - path.lineTo(xposm, fb_height); - canvas->drawPath(path, opts.theme.lcJoins); - } bool variantFile_info = mode == TILED && variantTracks.size() > 1 && yposm < fb_height * 0.02; if (variantFile_info) { @@ -1121,19 +1087,33 @@ namespace Manager { float height_f = fonts.overlayHeight * 2; float x = 50; float w = fb_width - 100; + + SkPaint bg; + if (opts.theme_str != "igv") { + bg.setARGB(255, 15, 15, 25); + } else { + bg.setARGB(255, 240, 240, 240); + } + if (x < w) { float y = fb_height - (fb_height * 0.025); - float y2 = fb_height - (height_f * 2.25); +// float y2 = fb_height - (height_f * 2.25); + float y2 = fb_height - (height_f * 2.5); float yy = (y2 < y) ? y2 : y; float to_cursor_width = fonts.overlay.measureText(inputText.substr(0, charIndex).c_str(), charIndex, SkTextEncoding::kUTF8); - SkPaint box{}; - box.setColor(SK_ColorGRAY); - box.setStrokeWidth(monitorScale); - box.setAntiAlias(true); - box.setStyle(SkPaint::kStroke_Style); - rect.setXYWH(x, yy, w, height_f); - canvas->drawRoundRect(rect, 5 * monitorScale, 5 * monitorScale, opts.theme.bgPaint); - canvas->drawRoundRect(rect, 5 * monitorScale, 5 * monitorScale, box); +// SkPaint box{}; +// box.setColor(SK_ColorGRAY); +// box.setStrokeWidth(monitorScale); +// box.setAntiAlias(true); +// box.setStyle(SkPaint::kStroke_Style); +// rect.setXYWH(x, yy, w, height_f); +// canvas->drawRoundRect(rect, 5 * monitorScale, 5 * monitorScale, opts.theme.bgPaint); +// canvas->drawRoundRect(rect, 5 * monitorScale, 5 * monitorScale, box); + + rect.setXYWH(0, yy, fb_width, fb_height); + + canvas->drawRect(rect, bg); + SkPath path; path.moveTo(x + 14 + to_cursor_width, yy + (fonts.overlayHeight * 0.3)); path.lineTo(x + 14 + to_cursor_width, yy + fonts.overlayHeight * 1.5); @@ -1154,6 +1134,27 @@ namespace Manager { yy -= pad + pad; SkPaint tip_paint = opts.theme.lcBright; tip_paint.setAntiAlias(true); + float n = 0; + for (const auto &cmd : Menu::commandToolTip) { + std::string cmd_s = cmd; + if (!inputText.empty() && !Utils::startsWith(cmd_s, inputText)) { + continue; + } + if (cmd_s == inputText) { + n = 0; + break; + } + n += 1; + } + if (n > 0) { + float step = fonts.overlayHeight + pad; + float top = yy - (n * step); + rect.setXYWH(0, top, x + fonts.overlayWidth * 18, (n * step) + pad + pad); + canvas->drawRect(rect, bg); + } + + + for (const auto &cmd : Menu::commandToolTip) { std::string cmd_s = cmd; if (!inputText.empty() && !Utils::startsWith(cmd_s, inputText)) { @@ -1163,7 +1164,7 @@ namespace Manager { break; } rect.setXYWH(x, yy - fonts.overlayHeight - pad, fonts.overlayWidth * 18, fonts.overlayHeight + pad + pad); - canvas->drawRoundRect(rect, 5 * monitorScale, 5 * monitorScale, opts.theme.bgPaint); +// canvas->drawRoundRect(rect, 5 * monitorScale, 5 * monitorScale, opts.theme.bgPaint); sk_sp blob = SkTextBlob::MakeFromString(cmd, fonts.overlay); canvas->drawTextBlob(blob, x + (fonts.overlayWidth * 3), yy, opts.theme.tcDel); tip_paint.setStyle(SkPaint::kStrokeAndFill_Style); @@ -1187,6 +1188,52 @@ namespace Manager { } } } + + float half_h = (float)fb_height / 2; + bool tool_popup = (xposm > 0 && xposm <= 60 && yposm >= half_h - 150 && yposm <= half_h + 150 && (xDrag == -1000000 || xDrag == 0) && (yDrag == -1000000 || yDrag == 0)); + if (tool_popup) { + SkRect rect{}; + SkPaint pop_paint = opts.theme.bgPaint; + pop_paint.setStrokeWidth(monitorScale); + pop_paint.setStyle(SkPaint::kStrokeAndFill_Style); + pop_paint.setAntiAlias(true); + pop_paint.setAlpha(255); + rect.setXYWH(-10, half_h - 35 * monitorScale, 30 * monitorScale + 10, 70 * monitorScale); + canvas->drawRoundRect(rect, 5 * monitorScale, 5 * monitorScale, pop_paint); + + SkPaint cog_paint = opts.theme.lcBright; + cog_paint.setAntiAlias(true); + cog_paint.setStrokeWidth(3 * monitorScale); + cog_paint.setStyle(SkPaint::kStrokeAndFill_Style); + canvas->drawCircle(15 * monitorScale, half_h - 12.5 * monitorScale, 5 * monitorScale, cog_paint); + + SkPath path; + path.moveTo(15 * monitorScale, half_h - (22.5 * monitorScale)); + path.lineTo(15 * monitorScale, half_h - (2.5 * monitorScale)); + canvas->drawPath(path, cog_paint); + path.moveTo(5 * monitorScale, half_h - 12.5 * monitorScale); + path.lineTo((25 * monitorScale), half_h - 12.5 * monitorScale); + canvas->drawPath(path, cog_paint); + path.moveTo(8 * monitorScale, half_h - (19.5 * monitorScale)); + path.lineTo(22 * monitorScale, half_h - (5.5 * monitorScale)); + canvas->drawPath(path, cog_paint); + path.moveTo(8 * monitorScale, half_h - (5.5 * monitorScale)); + path.lineTo(22 * monitorScale, half_h - (19.5 * monitorScale)); + canvas->drawPath(path, cog_paint); + canvas->drawCircle(15 * monitorScale, half_h - 12.5 * monitorScale, 2.5 * monitorScale, pop_paint); + + rect.setXYWH(7.5 * monitorScale, half_h + 7.5 * monitorScale, 15 * monitorScale, 15 * monitorScale); + cog_paint.setStrokeWidth(monitorScale); + cog_paint.setStyle(SkPaint::kStroke_Style); + canvas->drawRoundRect(rect, 3.5 * monitorScale, 3.5 * monitorScale, cog_paint); + + } else if (drawLine && mode != SETTINGS) { + SkPath path; + path.moveTo(xposm, 0); + path.lineTo(xposm, fb_height); + canvas->drawPath(path, opts.theme.lcJoins); + } + bool current_view_is_images = (!variantTracks.empty() && variantTracks[variantFileSelection].type == HGW::TrackType::IMAGES); if (bams.empty() && !current_view_is_images && mode != SETTINGS) { float trackBoundary = fb_height - totalTabixY - refSpace; @@ -1351,7 +1398,7 @@ namespace Manager { setScaling(); canvas->drawPaint(opts.theme.bgPaint); for (auto &cl: collections) { - Drawing::drawCollection(opts, cl, canvas, trackY, yScaling, fonts, opts.link_op, refSpace, pointSlop, textDrop, pH); + 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); @@ -1416,10 +1463,10 @@ namespace Manager { 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); + 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); + filters, opts, canvas, trackY, yScaling, fonts, refSpace, pool, pointSlop, textDrop, pH, monitorScale); } idx += 1; } diff --git a/src/segments.cpp b/src/segments.cpp index d2e6fa4..59dfd11 100644 --- a/src/segments.cpp +++ b/src/segments.cpp @@ -271,45 +271,37 @@ namespace Segs { self->left_soft_clip = 0; self->right_soft_clip = 0; - uint32_t last_op = 0; + uint32_t seq_index = 0; self->any_ins.reserve(cigar_l); - self->block_starts.reserve(cigar_l); - self->block_ends.reserve(cigar_l); + self->blocks.reserve(cigar_l); -// uint32_t min_gap = 1000; // todo THISSS -// uint32_t last_l = 0; for (k = 0; k < cigar_l; k++) { op = cigar_p[k] & BAM_CIGAR_MASK; l = cigar_p[k] >> BAM_CIGAR_SHIFT; switch (op) { case BAM_CMATCH: case BAM_CEQUAL: case BAM_CDIFF: - if (last_op == BAM_CINS ) { //|| (last_op == BAM_CDEL && last_l < min_gap)) { - if (!self->block_ends.empty() ) { - self->block_ends.back() = pos + l; - } - } else { - self->block_starts.push_back(pos); - self->block_ends.push_back(pos + l); - } + self->blocks.emplace_back() = {pos, pos+l, seq_index}; pos += l; + seq_index += l; break; case BAM_CINS: self->any_ins.push_back({pos, l}); + seq_index += l; break; case BAM_CDEL: pos += l; -// last_l = l; break; case BAM_CREF_SKIP: op = BAM_CDEL; pos += l; -// last_l = l; + seq_index += l; break; case BAM_CSOFT_CLIP: if (k == 0) { self->left_soft_clip = (int)l; + seq_index += l; } else { self->right_soft_clip = (int)l; } @@ -319,9 +311,8 @@ namespace Segs { default: // Match case --> MATCH, EQUAL, DIFF break; } - last_op = op; } - self->reference_end = self->block_ends.back(); + self->reference_end = self->blocks.back().end; self->cov_start = (int)self->pos - self->left_soft_clip; self->cov_end = (int)self->reference_end + self->right_soft_clip; @@ -332,23 +323,29 @@ namespace Segs { } else { self->has_SA = false; } - bool has_mods = (bam_aux_get(self->delegate, "MM") != nullptr || bam_aux_get(self->delegate, "Mm") != nullptr); + bool has_mods = (src->core.l_qseq > 0 && (bam_aux_get(self->delegate, "MM") != nullptr || bam_aux_get(self->delegate, "Mm") != nullptr)); if (has_mods && parse_mods) { hts_base_mod_state * mod_state = hts_base_mod_state_alloc(); int res = bam_parse_basemod(src, mod_state); if (res >= 0) { hts_base_mod mods[10]; - int pos = 0; + int pos = 0; // position on read, not reference int nm = bam_next_basemod(src, mod_state, mods, 10, &pos); while (nm > 0) { self->any_mods.emplace_back() = ModItem(); ModItem& mi = self->any_mods.back(); mi.index = pos; - for (size_t m=0; m < std::min(4, nm); ++m) { - mi.mods[m] = (char)mods[m].modified_base; - mi.quals[m] = (uint8_t)mods[m].qual; - mi.strands[m] = (bool)mods[m].strand; + size_t j=0; + int thresh = 50; + for (size_t m=0; m < std::min((size_t)4, (size_t)nm); ++m) { + if (mods[m].qual > thresh) { + mi.mods[j] = (char)mods[m].modified_base; + mi.quals[j] = (uint8_t)mods[m].qual; + mi.strands[j] = (bool)mods[m].strand; + j += 1; + } } + mi.n_mods = (uint8_t)j; // std::cout << nm << " " << mods[0].modified_base // << " " << mods[0].canonical_base // << " " << mods[0].strand @@ -358,6 +355,7 @@ namespace Segs { } // std::cout << std::endl; } + hts_base_mod_state_free(mod_state); } @@ -392,8 +390,9 @@ namespace Segs { } void align_clear(Align *self) { - self->block_starts.clear(); - self->block_ends.clear(); +// self->block_starts.clear(); +// self->block_ends.clear(); + self->blocks.clear(); self->any_ins.clear(); } @@ -444,11 +443,13 @@ namespace Segs { } void addToCovArray(std::vector &arr, const Align &align, const uint32_t begin, const uint32_t end, const uint32_t l_arr) noexcept { - size_t n_blocks = align.block_starts.size(); + size_t n_blocks = align.blocks.size(); +// size_t n_blocks = align.block_starts.size(); for (size_t idx=0; idx < n_blocks; ++idx) { - uint32_t block_s = align.block_starts[idx]; +// uint32_t block_s = align.block_starts[idx]; + uint32_t block_s = align.blocks[idx].start; if (block_s >= end) { break; } - uint32_t block_e = align.block_ends[idx]; + uint32_t block_e = align.blocks[idx].end; if (block_e < begin) { continue; } uint32_t s = std::max(block_s, begin) - begin; uint32_t e = std::min(block_e, end) - begin; @@ -610,13 +611,19 @@ namespace Segs { return samMaxY; } - constexpr char lookup_ref_base[256] = { - ['A'] = 1, ['a'] = 1, - ['C'] = 2, ['c'] = 2, - ['G'] = 4, ['g'] = 4, - ['T'] = 8, ['t'] = 8, - ['N'] = 15, ['n'] = 15, // All other entries default to 15 - }; + constexpr std::array make_lookup_ref_base() { + std::array a{}; + for (auto& elem : a) { + elem = 15; // Initialize all elements to 15 + } + a['A'] = 1; a['a'] = 1; + a['C'] = 2; a['c'] = 2; + a['G'] = 4; a['g'] = 4; + a['T'] = 8; a['t'] = 8; + a['N'] = 15; a['n'] = 15; + return a; + } + constexpr std::array lookup_ref_base = make_lookup_ref_base(); void update_A(Mismatches& elem) { elem.A += 1; } void update_C(Mismatches& elem) { elem.C += 1; } @@ -644,6 +651,7 @@ namespace Segs { update_pass // 15 }; + // used for drawing mismatches over coverage track void findMismatches(const Themes::IniOptions &opts, ReadCollection &collection) { std::vector &mm_array = collection.mmVector; diff --git a/src/segments.h b/src/segments.h index 68e2400..9efdb35 100644 --- a/src/segments.h +++ b/src/segments.h @@ -33,12 +33,18 @@ namespace Segs { // typedef int64_t hts_pos_t; + struct EXPORT ABlock { + uint32_t start, end; // on reference + uint32_t seq_index; + }; + struct EXPORT InsItem { uint32_t pos, length; }; struct EXPORT ModItem { // up to 4 modifications int index; + uint8_t n_mods; char mods[4]; // 0 is used to indicate no more mods uint8_t quals[4]; bool strands[4]; @@ -74,8 +80,8 @@ namespace Segs { bam1_t *delegate; int cov_start, cov_end, orient_pattern, left_soft_clip, right_soft_clip, y, edge_type; uint32_t pos, reference_end; - bool has_SA; //, initialized; - std::vector block_starts, block_ends; + bool has_SA; + std::vector blocks; std::vector any_ins; std::vector any_mods; diff --git a/src/themes.cpp b/src/themes.cpp index e3c2da4..8d49807 100644 --- a/src/themes.cpp +++ b/src/themes.cpp @@ -367,6 +367,7 @@ namespace Themes { soft_clip_threshold = 20000; small_indel_threshold = 100000; snp_threshold = 1000000; + mod_threshold = 250000; edge_highlights = 100000; variant_distance = 100000; low_memory = 1500000; @@ -471,6 +472,11 @@ namespace Themes { if (myIni["view_thresholds"].has("low_memory")) { low_memory = std::stoi(myIni["view_thresholds"]["low_memory"]); } + if (myIni["view_thresholds"].has("mod_threshold")) { + mod_threshold = std::stoi(myIni["view_thresholds"]["mod_threshold"]); + } else { + myIni["view_thresholds"]["mod"] = "1000000"; + } scroll_right = key_table[myIni["navigation"]["scroll_right"]]; scroll_left = key_table[myIni["navigation"]["scroll_left"]]; @@ -757,7 +763,6 @@ namespace Themes { sub["theme"] = theme_str; sub["dimensions"] = std::to_string(dimensions.x) + "x" + std::to_string(dimensions.y); -// sub["indel_length"] = std::to_string(indel_length); sub["ylim"] = std::to_string(ylim); sub["coverage"] = (max_coverage) ? "true" : "false"; sub["log2_cov"] = (log2_cov) ? "true" : "false"; @@ -773,6 +778,7 @@ namespace Themes { vt["soft_clip"] = std::to_string(soft_clip_threshold); vt["small_indel"] = std::to_string(small_indel_threshold); vt["snp"] = std::to_string(snp_threshold); + vt["mod"] = std::to_string(mod_threshold); vt["edge_highlights"] = std::to_string(edge_highlights); mINI::INIMap& lb = seshIni["labelling"]; diff --git a/src/themes.h b/src/themes.h index ca4d763..af29ffd 100644 --- a/src/themes.h +++ b/src/themes.h @@ -165,7 +165,7 @@ namespace Themes { int enter_interactive_mode; int repeat_command; int start_index; - int soft_clip_threshold, small_indel_threshold, snp_threshold, variant_distance, low_memory; + int soft_clip_threshold, small_indel_threshold, snp_threshold, mod_threshold, variant_distance, low_memory; int edge_highlights; int font_size; From 1fa5b543579c6b66e7fc2a1ca08bc56868ed90e0 Mon Sep 17 00:00:00 2001 From: kcleal Date: Fri, 5 Jul 2024 16:19:40 +0100 Subject: [PATCH 44/51] Work on print function. Nicer ins drawing --- src/drawing.cpp | 54 ++--- src/plot_controls.cpp | 4 +- src/plot_manager.cpp | 4 +- src/segments.cpp | 450 ++++++++++++++++++++++++++++++++++++++++-- src/term_out.cpp | 124 ++++++++++-- src/term_out.h | 2 +- 6 files changed, 581 insertions(+), 57 deletions(-) diff --git a/src/drawing.cpp b/src/drawing.cpp index 34f7f4c..5c2d8b5 100644 --- a/src/drawing.cpp +++ b/src/drawing.cpp @@ -1097,33 +1097,7 @@ namespace Drawing { } } - // add insertions - if (!a.any_ins.empty()) { - for (auto &ins: a.any_ins) { - float p = (ins.pos - regionBegin) * xScaling; - if (0 <= p && p < regionPixels) { - std::sprintf(indelChars, "%d", ins.length); - size_t sl = strlen(indelChars); - textW = fonts.textWidths[sl - 1]; - if (ins.length > (uint32_t) opts.indel_length) { - if (regionLen < 500000 && indelTextFits) { // line and text - drawIns(canvas, Y, p, yScaling, xOffset, yOffset, textW, theme.insS, - theme.fcIns, path, rect, pH, monitorScale); - text_ins.emplace_back() = {SkTextBlob::MakeFromString(indelChars, fonts.overlay), - (float)(p - (textW * 0.5) + xOffset - 2), - ((Y + polygonHeight) * yScaling) + yOffset - textDrop}; - } else { // line only - drawIns(canvas, Y, p, yScaling, xOffset, yOffset, xScaling, theme.insS, - theme.fcIns, path, rect, pH, monitorScale); - } - } else if (regionLen < 100000 && regionLen < opts.small_indel_threshold) { // line only - drawIns(canvas, Y, p, yScaling, xOffset, yOffset, xScaling, theme.insS, - theme.fcIns, path, rect, pH, monitorScale); - } - } - } - } // add mismatches // if (regionLen > opts.snp_threshold && plotSoftClipAsBlock) { @@ -1155,6 +1129,34 @@ namespace Drawing { yScaledOffset, pH, l_qseq, monitorScale); } + // add insertions + if (!a.any_ins.empty()) { + for (auto &ins: a.any_ins) { + float p = (ins.pos - regionBegin) * xScaling; + if (0 <= p && p < regionPixels) { + std::sprintf(indelChars, "%d", ins.length); + size_t sl = strlen(indelChars); + textW = fonts.textWidths[sl - 1]; + if (ins.length > (uint32_t) opts.indel_length) { + if (regionLen < 500000 && indelTextFits) { // line and text + drawIns(canvas, Y, p, yScaling, xOffset, yOffset, textW, theme.insS, + theme.fcIns, path, rect, pH, monitorScale); + text_ins.emplace_back() = {SkTextBlob::MakeFromString(indelChars, fonts.overlay), + (float)(p - (textW * 0.5) + xOffset - 2), + ((Y + polygonHeight) * yScaling) + yOffset - textDrop}; + + } else { // line only + drawIns(canvas, Y, p, yScaling, xOffset, yOffset, xScaling, theme.insS, + theme.fcIns, path, rect, pH, monitorScale); + } + } else if (regionLen < 100000 && regionLen < opts.small_indel_threshold) { // line only + drawIns(canvas, Y, p, yScaling, xOffset, yOffset, xScaling, theme.insS, + theme.fcIns, path, rect, pH, monitorScale); + } + } + } + } + // add soft-clips if (!plotSoftClipAsBlock) { uint8_t *ptr_seq = bam_get_seq(a.delegate); diff --git a/src/plot_controls.cpp b/src/plot_controls.cpp index 89ed2fd..eb0adf3 100644 --- a/src/plot_controls.cpp +++ b/src/plot_controls.cpp @@ -1334,7 +1334,7 @@ namespace Manager { } else { bnd->edge_type = 4; target_qname = bam_get_qname(bnd->delegate); - Term::printRead(bnd, headers[cl.bamIdx], selectedAlign, cl.region->refSeq, cl.region->start, cl.region->end, opts.low_memory, out); + Term::printRead(bnd, headers[cl.bamIdx], selectedAlign, cl.region->refSeq, cl.region->start, cl.region->end, opts.low_memory, out, pos); } redraw = true; processed = true; @@ -1356,7 +1356,7 @@ namespace Manager { } else { bnd->edge_type = 4; target_qname = bam_get_qname(bnd->delegate); - Term::printRead(bnd, headers[cl.bamIdx], selectedAlign, cl.region->refSeq, cl.region->start, cl.region->end, opts.low_memory, out); + Term::printRead(bnd, headers[cl.bamIdx], selectedAlign, cl.region->refSeq, cl.region->start, cl.region->end, opts.low_memory, out, pos); } redraw = true; processed = true; diff --git a/src/plot_manager.cpp b/src/plot_manager.cpp index 26b274e..377393b 100644 --- a/src/plot_manager.cpp +++ b/src/plot_manager.cpp @@ -910,7 +910,7 @@ namespace Manager { pointSlop, textDrop, pH, monitorScale); } } else { - std::cout << " full draw\n"; +// std::cout << " full draw\n"; Drawing::drawCollection(opts, cl, canvasR, trackY, yScaling, fonts, opts.link_op, refSpace, pointSlop, textDrop, pH, monitorScale); } @@ -918,7 +918,7 @@ namespace Manager { canvasR->restore(); } - std::cout << " done draw\n"; +// std::cout << " done draw\n"; if (opts.max_coverage) { Drawing::drawCoverage(opts, collections, canvasR, fonts, covY, refSpace); } diff --git a/src/segments.cpp b/src/segments.cpp index 59dfd11..2adef27 100644 --- a/src/segments.cpp +++ b/src/segments.cpp @@ -236,6 +236,440 @@ namespace Segs { // } // } + + /* + * Count frequency of A, C, G, T and N canonical bases in the sequence + */ + #define MAX_BASE_MOD 256 + struct hts_base_mod_state { + int type[MAX_BASE_MOD]; // char or minus-CHEBI + int canonical[MAX_BASE_MOD];// canonical base, as seqi (1,2,4,8,15) + char strand[MAX_BASE_MOD]; // strand of modification; + or - + int MMcount[MAX_BASE_MOD]; // no. canonical bases left until next mod + char *MM[MAX_BASE_MOD]; // next pos delta (string) + char *MMend[MAX_BASE_MOD]; // end of pos-delta string + uint8_t *ML[MAX_BASE_MOD]; // next qual + int MLstride[MAX_BASE_MOD]; // bytes between quals for this type + int implicit[MAX_BASE_MOD]; // treat unlisted positions as non-modified? + int seq_pos; // current position along sequence + int nmods; // used array size (0 to MAX_BASE_MOD-1). + uint32_t flags; // Bit-field: see HTS_MOD_REPORT_UNCHECKED + }; + + static void seq_freq(const bam1_t *b, int freq[16]) { + int i; + memset(freq, 0, 16*sizeof(*freq)); + uint8_t *seq = bam_get_seq(b); + for (i = 0; i < b->core.l_qseq; i++) + freq[bam_seqi(seq, i)]++; + freq[15] = b->core.l_qseq; // all bases count as N for base mods + } + + //0123456789ABCDEF + //=ACMGRSVTWYHKDBN aka seq_nt16_str[] + //=TGKCYSBAWRDMHVN comp1ement of seq_nt16_str + //084C2A6E195D3B7F + static int seqi_rc[] = { 0,8,4,12,2,10,6,14,1,9,5,13,3,11,7,15 }; + + /* + * Parse the MM and ML tags to populate the base mod state. + * This structure will have been previously allocated via + * hts_base_mod_state_alloc, but it does not need to be repeatedly + * freed and allocated for each new bam record. (Although obviously + * it requires a new call to this function.) + * + * Flags are copied into the state and used to control reporting functions. + * Currently the only flag is HTS_MOD_REPORT_UNCHECKED, to control whether + * explicit "C+m?" mods report quality HTS_MOD_UNCHECKED for the bases + * outside the explicitly reported region. + */ + int bam_parse_basemod_gw(const bam1_t *b, hts_base_mod_state *state, + uint32_t flags) { + // Reset position, else upcoming calls may fail on + // seq pos - length comparison + state->seq_pos = 0; + state->nmods = 0; + state->flags = flags; + + // Read MM and ML tags + uint8_t *mm = bam_aux_get(b, "MM"); + if (!mm) mm = bam_aux_get(b, "Mm"); + if (!mm) + return 0; + if (mm[0] != 'Z') { +#ifdef DEBUG + hts_log_error("%s: MM tag is not of type Z", bam_get_qname(b)); +#endif + return -1; + } + + uint8_t *mi = bam_aux_get(b, "MN"); + if (mi && bam_aux2i(mi) != b->core.l_qseq && b->core.l_qseq) { + // bam_aux2i with set errno = EINVAL and return 0 if the tag + // isn't integer, but 0 will be a seq-length mismatch anyway so + // triggers an error here too. +#ifdef DEBUG + hts_log_error("%s: MM/MN data length is incompatible with" + " SEQ length", bam_get_qname(b)); +#endif + return -1; + } + + uint8_t *ml = bam_aux_get(b, "ML"); + if (!ml) ml = bam_aux_get(b, "Ml"); + if (ml && (ml[0] != 'B' || ml[1] != 'C')) { +#ifdef DEBUG + hts_log_error("%s: ML tag is not of type B,C", bam_get_qname(b)); +#endif + return -1; + } + uint8_t *ml_end = ml ? ml+6 + le_to_u32(ml+2) : NULL; + if (ml) ml += 6; + + // Aggregate freqs of ACGTN if reversed, to get final-delta (later) + int freq[16]; + if (b->core.flag & BAM_FREVERSE) + Segs::seq_freq(b, freq); + + char *cp = (char *)mm+1; + int mod_num = 0; + int implicit = 1; + while (*cp) { + for (; *cp; cp++) { + // cp should be [ACGTNU][+-]([a-zA-Z]+|[0-9]+)[.?]?(,\d+)*; + unsigned char btype = *cp++; + + if (btype != 'A' && btype != 'C' && + btype != 'G' && btype != 'T' && + btype != 'U' && btype != 'N') + return -1; + if (btype == 'U') btype = 'T'; + + btype = seq_nt16_table[btype]; + + // Strand + if (*cp != '+' && *cp != '-') + return -1; // malformed + char strand = *cp++; + + // List of modification types + char *ms = cp, *me; // mod code start and end + char *cp_end = NULL; + int chebi = 0; + if (std::isdigit(*cp)) { + chebi = strtol(cp, &cp_end, 10); + cp = cp_end; + ms = cp-1; + } else { + while (*cp && std::isalpha(*cp)) + cp++; + if (*cp == '\0') + return -1; + } + + me = cp; + + // Optional explicit vs implicit marker + implicit = 1; + if (*cp == '.') { + // default is implicit = 1; + cp++; + } else if (*cp == '?') { + implicit = 0; + cp++; + } else if (*cp != ',' && *cp != ';') { + // parse error + return -1; + } + + long delta; + int n = 0; // nth symbol in a multi-mod string + int stride = me-ms; + int ndelta = 0; + + if (b->core.flag & BAM_FREVERSE) { + // We process the sequence in left to right order, + // but delta is successive count of bases to skip + // counting right to left. This also means the number + // of bases to skip at left edge is unrecorded (as it's + // the remainder). + // + // To output mods in left to right, we step through the + // MM list in reverse and need to identify the left-end + // "remainder" delta. + int total_seq = 0; + for (;;) { + cp += (*cp == ','); + if (*cp == 0 || *cp == ';') + break; + + delta = strtol(cp, &cp_end, 10); + if (cp_end == cp) { +#ifdef DEBUG + hts_log_error("%s: Hit end of MM tag. Missing " + "semicolon?", bam_get_qname(b)); +#endif + return -1; + } + + cp = cp_end; + total_seq += delta+1; + ndelta++; + } + delta = freq[seqi_rc[btype]] - total_seq; // remainder + } else { + delta = *cp == ',' + ? strtol(cp+1, &cp_end, 10) + : 0; + if (!cp_end) { + // empty list + delta = INT_MAX; + cp_end = cp; + } + } + // Now delta is first in list or computed remainder, + // and cp_end is either start or end of the MM list. + while (ms < me) { + state->type [mod_num] = chebi ? -chebi : *ms; + state->strand [mod_num] = (strand == '-'); + state->canonical[mod_num] = btype; + state->MLstride [mod_num] = stride; + state->implicit [mod_num] = implicit; + + if (delta < 0) { +#ifdef DEBUG + hts_log_error("%s: MM tag refers to bases beyond sequence " + "length", bam_get_qname(b)); +#endif + return -1; + } + state->MMcount [mod_num] = delta; + if (b->core.flag & BAM_FREVERSE) { + state->MM [mod_num] = me+1; + state->MMend[mod_num] = cp_end; + state->ML [mod_num] = ml ? ml+n +(ndelta-1)*stride: NULL; + } else { + state->MM [mod_num] = cp_end; + state->MMend[mod_num] = NULL; + state->ML [mod_num] = ml ? ml+n : NULL; + } + + if (++mod_num >= MAX_BASE_MOD) { +#ifdef DEBUG + hts_log_error("%s: Too many base modification types", + bam_get_qname(b)); +#endif + return -1; + } + ms++; n++; + } + + // Skip modification deltas + if (ml) { + if (b->core.flag & BAM_FREVERSE) { + ml += ndelta*stride; + } else { + while (*cp && *cp != ';') { + if (*cp == ',') + ml+=stride; + cp++; + } + } + if (ml > ml_end) { +#ifdef DEBUG + hts_log_error("%s: Insufficient number of entries in ML " + "tag", bam_get_qname(b)); +#endif + return -1; + } + } else { + // cp_end already known if FREVERSE + if (cp_end && (b->core.flag & BAM_FREVERSE)) + cp = cp_end; + else + while (*cp && *cp != ';') + cp++; + } + if (!*cp) { +#ifdef DEBUG + hts_log_error("%s: Hit end of MM tag. Missing semicolon?", + bam_get_qname(b)); +#endif + return -1; + } + } + } + if (ml && ml != ml_end) { +#ifdef DEBUG + hts_log_error("%s: Too many entries in ML tag", bam_get_qname(b)); +#endif + return -1; + } + + state->nmods = mod_num; + + return 0; + } + + int bam_mods_at_next_pos(const bam1_t *b, hts_base_mod_state *state, + hts_base_mod *mods, int n_mods) { + if (b->core.flag & BAM_FREVERSE) { + if (state->seq_pos < 0) + return -1; + } else { + if (state->seq_pos >= b->core.l_qseq) + return -1; + } + + int i, j, n = 0; + unsigned char base = bam_seqi(bam_get_seq(b), state->seq_pos); + state->seq_pos++; + if (b->core.flag & BAM_FREVERSE) + base = seqi_rc[base]; + + for (i = 0; i < state->nmods; i++) { + int unchecked = 0; + if (state->canonical[i] != base && state->canonical[i] != 15/*N*/) + continue; + + if (state->MMcount[i]-- > 0) { + if (!state->implicit[i] && + (state->flags & HTS_MOD_REPORT_UNCHECKED)) + unchecked = 1; + else + continue; + } + + char *MMptr = state->MM[i]; + if (n < n_mods) { + mods[n].modified_base = state->type[i]; + mods[n].canonical_base = seq_nt16_str[state->canonical[i]]; + mods[n].strand = state->strand[i]; + mods[n].qual = unchecked + ? HTS_MOD_UNCHECKED + : (state->ML[i] ? *state->ML[i] : HTS_MOD_UNKNOWN); + } + n++; + + if (unchecked) + continue; + + if (state->ML[i]) + state->ML[i] += (b->core.flag & BAM_FREVERSE) + ? -state->MLstride[i] + : +state->MLstride[i]; + + if (b->core.flag & BAM_FREVERSE) { + // process MM list backwards + char *cp; + if (state->MMend[i]-1 < state->MM[i]) { + // Should be impossible to hit if coding is correct +#ifdef DEBUG + hts_log_error("Assert failed while processing base modification states"); +#endif + return -1; + } + for (cp = state->MMend[i]-1; cp != state->MM[i]; cp--) + if (*cp == ',') + break; + state->MMend[i] = cp; + if (cp != state->MM[i]) + state->MMcount[i] = strtol(cp+1, NULL, 10); + else + state->MMcount[i] = INT_MAX; + } else { + if (*state->MM[i] == ',') + state->MMcount[i] = strtol(state->MM[i]+1, &state->MM[i], 10); + else + state->MMcount[i] = INT_MAX; + } + + // Multiple mods at the same coords. + for (j=i+1; j < state->nmods && state->MM[j] == MMptr; j++) { + if (n < n_mods) { + mods[n].modified_base = state->type[j]; + mods[n].canonical_base = seq_nt16_str[state->canonical[j]]; + mods[n].strand = state->strand[j]; + mods[n].qual = state->ML[j] ? *state->ML[j] : -1; + } + n++; + state->MMcount[j] = state->MMcount[i]; + state->MM[j] = state->MM[i]; + if (state->ML[j]) + state->ML[j] += (b->core.flag & BAM_FREVERSE) + ? -state->MLstride[j] + : +state->MLstride[j]; + } + i = j-1; + } + return n; + } + + int bam_next_basemod(const bam1_t *b, hts_base_mod_state *state, + hts_base_mod *mods, int n_mods, int *pos) { + // Look through state->MMcount arrays to see when the next lowest is + // per base type; + int next[16], freq[16] = {0}, i; + memset(next, 0x7f, 16*sizeof(*next)); + const int unchecked = state->flags & HTS_MOD_REPORT_UNCHECKED; + if (b->core.flag & BAM_FREVERSE) { + for (i = 0; i < state->nmods; i++) { + if (unchecked && !state->implicit[i]) + next[seqi_rc[state->canonical[i]]] = 1; + else if (next[seqi_rc[state->canonical[i]]] > state->MMcount[i]) + next[seqi_rc[state->canonical[i]]] = state->MMcount[i]; + } + } else { + for (i = 0; i < state->nmods; i++) { + if (unchecked && !state->implicit[i]) + next[state->canonical[i]] = 0; + else if (next[state->canonical[i]] > state->MMcount[i]) + next[state->canonical[i]] = state->MMcount[i]; + } + } + + // Now step through the sequence counting off base types. + for (i = state->seq_pos; i < b->core.l_qseq; i++) { + unsigned char bc = bam_seqi(bam_get_seq(b), i); + if (next[bc] <= freq[bc] || next[15] <= freq[15]) + break; + freq[bc]++; + if (bc != 15) // N + freq[15]++; + } + *pos = state->seq_pos = i; + + if (b->core.flag & BAM_FREVERSE) { + for (i = 0; i < state->nmods; i++) + state->MMcount[i] -= freq[seqi_rc[state->canonical[i]]]; + } else { + for (i = 0; i < state->nmods; i++) + state->MMcount[i] -= freq[state->canonical[i]]; + } + + if (b->core.l_qseq && state->seq_pos >= b->core.l_qseq && + !(b->core.flag & BAM_FREVERSE)) { + // Spots +ve orientation run-overs. + // The -ve orientation is spotted in bam_parse_basemod2 + int i; + for (i = 0; i < state->nmods; i++) { + // Check if any remaining items in MM after hitting the end + // of the sequence. + if (state->MMcount[i] < 0x7f000000 || + (*state->MM[i]!=0 && *state->MM[i]!=';')) { +#ifdef DEBUG + hts_log_warning("MM tag refers to bases beyond sequence length"); +#endif + return -1; + } + } + return 0; + } + + int r = bam_mods_at_next_pos(b, state, mods, n_mods); + return r > 0 ? r : 0; + } + constexpr uint32_t PP_RR_MR = 50; alignas(64) constexpr std::array posFirst = {INV_F, u, u, u, u, u, u, u, @@ -323,10 +757,10 @@ namespace Segs { } else { self->has_SA = false; } - bool has_mods = (src->core.l_qseq > 0 && (bam_aux_get(self->delegate, "MM") != nullptr || bam_aux_get(self->delegate, "Mm") != nullptr)); - if (has_mods && parse_mods) { - hts_base_mod_state * mod_state = hts_base_mod_state_alloc(); - int res = bam_parse_basemod(src, mod_state); + + if (parse_mods) { + hts_base_mod_state* mod_state = new hts_base_mod_state; + int res = bam_parse_basemod_gw(src, mod_state, 0); if (res >= 0) { hts_base_mod mods[10]; int pos = 0; // position on read, not reference @@ -346,16 +780,10 @@ namespace Segs { } } mi.n_mods = (uint8_t)j; -// std::cout << nm << " " << mods[0].modified_base -// << " " << mods[0].canonical_base -// << " " << mods[0].strand -// << " " << mods[0].qual -// << std::endl; nm = bam_next_basemod(src, mod_state, mods, 10, &pos); } -// std::cout << std::endl; } - hts_base_mod_state_free(mod_state); + delete mod_state; } diff --git a/src/term_out.cpp b/src/term_out.cpp index 5c83313..703893a 100644 --- a/src/term_out.cpp +++ b/src/term_out.cpp @@ -325,35 +325,73 @@ namespace Term { } } - void printSeq(std::vector::iterator r, const char *refSeq, int refStart, int refEnd, int max, std::ostream& out) { + void printSeq(std::vector::iterator r, const char *refSeq, int refStart, int refEnd, int max, std::ostream& out, int pos, int indel_length=30) { auto l_seq = (int)r->delegate->core.l_qseq; if (l_seq == 0) { out << "*"; return; } - uint32_t l, cigar_l, op, k; + uint32_t cigar_l, op, k; + int l; uint32_t *cigar_p; cigar_l = r->delegate->core.n_cigar; cigar_p = bam_get_cigar(r->delegate); uint8_t *ptr_seq = bam_get_seq(r->delegate); int i = 0; int p = r->pos; + bool started = false; + int printed = 0; for (k = 0; k < cigar_l; k++) { op = cigar_p[k] & BAM_CIGAR_MASK; - l = cigar_p[k] >> BAM_CIGAR_SHIFT; - if (i >= max || (int)l >= max) { - out << "..."; - return; - } + l = (int)(cigar_p[k] >> BAM_CIGAR_SHIFT); + + int block_end = p + l; + if (op == BAM_CHARD_CLIP) { continue; } else if (op == BAM_CDEL) { - for (int n=0; n < (int)l; ++n) { - out << "-"; + if (started || std::abs(pos - p) < max || std::abs(pos - block_end) < max) { + started = true; + } else { + p += l; + continue; + } + started = true; + std::string str = std::to_string(l); + if (l > max) { + for (int n = 0; n < 100; ++n) { + out << "-"; + } + out << str; + for (int n = 0; n < 100; ++n) { + out << "-"; + } + } else if (l < (int)str.size() + 6) { + for (int n = 0; n < l; ++n) { + out << "-"; + } + } else { + int n_dash = ((l - (int)str.size()) / 2); + for (int n = 0; n < n_dash; ++n) { + out << "-"; + } + out << str; + for (int n = 0; n < n_dash; ++n) { + out << "-"; + } } p += l; + } else if (op == BAM_CMATCH) { - for (int n = 0; n < (int)l; ++n) { + if (std::abs(pos - p) < max || std::abs(pos - block_end)) { + started = true; + } else { + p += l; + i += l; + continue; + } + printed += l; + for (int n = 0; n < l; ++n) { uint8_t base = bam_seqi(ptr_seq, i); bool mm = false; if (p >= refStart && p < refEnd && refSeq != nullptr && std::toupper(refSeq[p - refStart]) != basemap[base]) { @@ -400,8 +438,20 @@ namespace Term { i += 1; p += 1; } + if (printed > max * 2) { + out << "..."; + break; + } } else if (op == BAM_CEQUAL) { + if (std::abs(pos - p) < max || std::abs(pos - block_end)) { + started = true; + } else { + p += l; + i += l; + continue; + } + printed += l; for (int n = 0; n < (int)l; ++n) { uint8_t base = bam_seqi(ptr_seq, i); switch (basemap[base]) { @@ -424,8 +474,25 @@ namespace Term { i += 1; } p += l; + if (printed > max * 2) { + out << "..."; + break; + } - } else if (op == BAM_CDIFF) { + } else if (started && (op == BAM_CDIFF || op == BAM_CINS)) { + if (std::abs(pos - p) < max || std::abs(pos - block_end)) { + started = true; + } else { + if (op == BAM_CDIFF) { + p += l; + } + i += l; + continue; + } + printed += l; + if (op == BAM_CINS && l > indel_length) { + out << termcolor::magenta << "[" << std::to_string(l) << "]" << termcolor::reset; + } for (int n = 0; n < (int)l; ++n) { uint8_t base = bam_seqi(ptr_seq, i); switch (basemap[base]) { @@ -447,10 +514,34 @@ namespace Term { } i += 1; } - p += l; + if (op == BAM_CDIFF) { + p += l; + } + if (op == BAM_CINS && printed > max * 2) { + out << "..."; + break; + } } else { - for (int n=0; n < (int)l; ++n) { // soft-clips + if (k == 0) { + if (pos - p > max) { + continue; + } else { + started = true; + } + } + int n = 0; + int stop = l; + if (l > max) { + if (k == 0) { + n = (int)l - max; + out << "..."; + } else { + stop = max; + } + } + i += n; + for (; n < stop; ++n) { // soft-clips uint8_t base = bam_seqi(ptr_seq, i); switch (basemap[base]) { case 65 : out << termcolor::green << "A" << termcolor::reset; break; @@ -461,6 +552,9 @@ namespace Term { } i += 1; } + if (l > max && k > 0) { + out << "..."; + } } } } @@ -476,7 +570,7 @@ namespace Term { return; } - void printRead(std::vector::iterator r, const sam_hdr_t* hdr, std::string &sam, const char *refSeq, int refStart, int refEnd, bool low_mem, std::ostream& out) { + void printRead(std::vector::iterator r, const sam_hdr_t* hdr, std::string &sam, const char *refSeq, int refStart, int refEnd, bool low_mem, std::ostream& out, int pos) { const char *rname = sam_hdr_tid2name(hdr, r->delegate->core.tid); const char *rnext = sam_hdr_tid2name(hdr, r->delegate->core.mtid); out << std::endl << std::endl; @@ -488,7 +582,7 @@ namespace Term { out << termcolor::bold << "flag " << termcolor::reset << r->delegate->core.flag << std::endl; out << termcolor::bold << "mapq " << termcolor::reset << (int)r->delegate->core.qual << std::endl; out << termcolor::bold << "cigar " << termcolor::reset; printCigar(r, out); out << std::endl; - out << termcolor::bold << "seq " << termcolor::reset; printSeq(r, refSeq, refStart, refEnd, 500, out); out << std::endl << std::endl; + out << termcolor::bold << "seq " << termcolor::reset; printSeq(r, refSeq, refStart, refEnd, 500, out, pos); out << std::endl << std::endl; read2sam(r, hdr, sam, low_mem, out); } diff --git a/src/term_out.h b/src/term_out.h index 909a17a..e2dc5f2 100644 --- a/src/term_out.h +++ b/src/term_out.h @@ -29,7 +29,7 @@ namespace Term { void editInputText(std::string &inputText, const char *letter, int &charIndex); void printRead(std::vector::iterator r, const sam_hdr_t* hdr, std::string &sam, const char *refSeq, - int refStart, int refEnd, bool low_mem, std::ostream& out); + int refStart, int refEnd, bool low_mem, std::ostream& out, int pos); void printSelectedSam(std::string &sam, std::ostream& out); From 383fa13dffc10859231fe128d990fe082f81475f Mon Sep 17 00:00:00 2001 From: kcleal Date: Sat, 6 Jul 2024 23:46:19 +0100 Subject: [PATCH 45/51] Work on Ins drawing. Added more options to session file and menu. Bug fixes --- .gw.ini | 4 +- src/drawing.cpp | 183 +++++++++++------------------------------- src/main.cpp | 5 +- src/menu.cpp | 11 ++- src/menu.h | 6 +- src/plot_commands.cpp | 20 ++++- src/plot_controls.cpp | 12 ++- src/plot_manager.cpp | 56 ++++++++----- src/plot_manager.h | 2 + src/themes.cpp | 45 ++++++----- src/themes.h | 11 +-- 11 files changed, 154 insertions(+), 201 deletions(-) diff --git a/.gw.ini b/.gw.ini index f086181..88bc204 100644 --- a/.gw.ini +++ b/.gw.ini @@ -22,7 +22,7 @@ saccer3=https://github.com/kcleal/ref_genomes/releases/download/v0.1.0/sacCer3.f [general] theme=dark -dimensions=3000x1000 +dimensions=1366x800 indel_length=10 ylim=50 coverage=true @@ -37,7 +37,7 @@ tabix_track_height=0.3 font=Menlo font_size=14 sv_arcs=true -show_mods=true +mods=false session_file= [view_thresholds] diff --git a/src/drawing.cpp b/src/drawing.cpp index 5c2d8b5..e917fa0 100644 --- a/src/drawing.cpp +++ b/src/drawing.cpp @@ -37,6 +37,13 @@ namespace Drawing { float x, y; }; + struct TextItemIns{ + sk_sp text; + float x, y; +// float box_x, box_y; + float box_y, box_w; + }; + void drawCoverage(const Themes::IniOptions &opts, std::vector &collections, SkCanvas *canvas, const Themes::Fonts &fonts, const float covYh, const float refSpace) { @@ -421,29 +428,21 @@ namespace Drawing { inline void drawIns(SkCanvas *canvas, float y0, float start, float yScaling, float xOffset, - float yOffset, float textW, const SkPaint &sidesColor, const SkPaint &faceColor, SkPath &path, - SkRect &rect, float pH, float overhang) { + float yOffset, const SkPaint &faceColor, SkRect &rect, float pH, float overhang, float width) { + float x = start + xOffset; float y = y0 * yScaling; - float text_half = textW * 0.5; - float box_left = x - text_half - overhang; - float box_w = textW + overhang; - rect.setXYWH(box_left, y + yOffset, textW + overhang, pH); - canvas->drawRect(rect, faceColor); + float box_left = x - (width * 0.5); - rect.setXYWH(box_left - overhang, yOffset + y, box_w + overhang + overhang, overhang);//pH * 0.2); - canvas->drawRect(rect, faceColor); + rect.setXYWH(box_left, y + yOffset, width, pH); + canvas->drawRect(rect, faceColor); // middle bar + + rect.setXYWH(box_left - overhang, yOffset + y, overhang + width + overhang, overhang); + canvas->drawRect(rect, faceColor); // top bar + + rect.setXYWH(box_left - overhang, yOffset + y + pH - overhang, overhang + width + overhang, overhang); + canvas->drawRect(rect, faceColor); // bottom bar - rect.setXYWH(box_left - overhang, yOffset + y + pH - overhang, box_w + overhang + overhang, overhang);//pH * 0.2); - canvas->drawRect(rect, faceColor); -// path.reset(); -// path.moveTo(x - text_half - overhang, yOffset + y + pH_05); -// path.lineTo(x + text_half + overhang, yOffset + y + pH_05); -// path.moveTo(x - text_half - overhang, yOffset + y + pH_95); -// path.lineTo(x + text_half + overhang, yOffset + y + pH_95); -// path.moveTo(x, yOffset + y); -// path.lineTo(x, yOffset + y + pH); -// canvas->drawPath(path, sidesColor); } constexpr std::array make_lookup_ref_base() { @@ -693,7 +692,7 @@ namespace Drawing { void drawBlock(bool plotPointedPolygons, bool pointLeft, bool edged, float s, float e, float width, float pointSlop, float pH, float yScaledOffset, float xOffset, float regionPixels, - size_t idx, size_t nBlocks, int regionLen, + size_t nBlocks, int regionLen, const Segs::Align &a, SkCanvas *canvas, SkPath &path, SkRect &rect, SkPaint &faceColor, SkPaint &edgeColor) { @@ -714,7 +713,7 @@ namespace Drawing { void drawDeletionLine(const Segs::Align &a, SkCanvas *canvas, SkPath &path, const Themes::IniOptions &opts, const Themes::Fonts &fonts, - int regionBegin, size_t idx, int Y, int regionLen, int starti, int lastEndi, + int regionBegin, int Y, int regionLen, int starti, int lastEndi, float regionPixels, float xScaling, float yScaling, float xOffset, float yOffset, float textDrop, std::vector &text, bool indelTextFits) { @@ -840,7 +839,8 @@ namespace Drawing { SkPath path; const Themes::BaseTheme &theme = opts.theme; - static std::vector text_ins, text_del; + static std::vector text_ins; + static std::vector text_del; text_ins.clear(); text_del.clear(); @@ -853,6 +853,9 @@ namespace Drawing { float yOffset = cl.yOffset; float regionPixels = cl.regionPixels; + float ins_block_h = std::fmin(pH * 0.3, monitorScale * 2); + float ins_block_w = std::fmax(ins_block_h, xScaling * 0.5); + int min_gap_size = 1 + (1 / (cl.regionPixels / regionLen)); bool plotSoftClipAsBlock = cl.plotSoftClipAsBlock; //regionLen > opts.soft_clip_threshold; @@ -906,7 +909,7 @@ namespace Drawing { e = (double)a.blocks[idx - 1].end - regionBegin; width = (e - s) * xScaling; drawBlock(plotPointedPolygons, pointLeft, edged, (float) s * xScaling, (float) e, (float) width, - pointSlop, pH, yScaledOffset, xOffset, regionPixels, idx, nBlocks, regionLen, + pointSlop, pH, yScaledOffset, xOffset, regionPixels, nBlocks, regionLen, a, canvas, path, rect, faceColor, edgeColor); idx_begin = idx; } @@ -915,7 +918,7 @@ namespace Drawing { e = (double)a.blocks[idx - 1].end - regionBegin; width = (e - s) * xScaling; drawBlock(plotPointedPolygons, pointLeft, edged, (float) s * xScaling, (float) e, (float) width, - pointSlop, pH, yScaledOffset, xOffset, regionPixels, idx, nBlocks, regionLen, + pointSlop, pH, yScaledOffset, xOffset, regionPixels, nBlocks, regionLen, a, canvas, path, rect, faceColor, edgeColor); idx_begin = idx; @@ -929,7 +932,7 @@ namespace Drawing { } if (lastEnd <= regionEnd && regionBegin <= starti) { drawDeletionLine(a, canvas, path, opts, fonts, - regionBegin, idx, Y, regionLen, starti, lastEnd, + regionBegin, Y, regionLen, starti, lastEnd, regionPixels, xScaling, yScaling, xOffset, yOffset, textDrop, text_del, indelTextFits); } @@ -940,104 +943,10 @@ namespace Drawing { 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, idx, nBlocks, regionLen, + pointSlop, pH, yScaledOffset, xOffset, regionPixels, nBlocks, regionLen, a, canvas, path, rect, faceColor, edgeColor); } - // draw blocks -// idx = 0; -// size_t from = 0; -// for (; idx < nBlocks; ++idx) { -// if (idx > 0 && (a.blocks[idx].start - a.blocks[idx - 1].end < min_block_width)) { -// continue; -// } -// -// if (a.blocks[idx].end - a.blocks[idx].start < min_block_width) { -// continue; -// } -// s = (double)a.blocks[from].start - regionBegin; -// e = (double)a.blocks[idx].end - regionBegin; -// -// s = (s < 0) ? 0 : s; -// e = (e > regionLen) ? regionLen : e; -// width = (e - s) * xScaling; -// -// drawBlock(plotPointedPolygons, pointLeft, edged, (float) s * xScaling, (float) e, (float) width, -// pointSlop, pH, yScaledOffset, xOffset, regionPixels, idx, nBlocks, regionLen, -// a, canvas, path, rect, faceColor, edgeColor); -// -// from = idx; -// } -// if (from < nBlocks-1) { -// s = (double)a.blocks[from].start - regionBegin; -// e = (double)a.blocks.back().end - regionBegin; -// s = (s < 0) ? 0 : s; -// e = (e > regionLen) ? regionLen : e; -// width = (e - s) * xScaling; -// drawBlock(plotPointedPolygons, pointLeft, edged, (float) s * xScaling, (float) e, (float) width, -// pointSlop, pH, yScaledOffset, xOffset, regionPixels, idx, nBlocks, regionLen, -// a, canvas, path, rect, faceColor, edgeColor); -// } - - -// -// -// size_t extend_from = 0; -// -// idx = 0; -// for (; idx < nBlocks; ++idx) { -// -// // add lines and text between gaps -//// if (idx > 0 && lastEnd != starti) { -//// drawDeletionLine(a, canvas, path, opts, fonts, -//// regionBegin, idx, Y, regionLen, starti, lastEnd, -//// regionPixels, xScaling, yScaling, xOffset, yOffset, -//// textDrop, text_del, indelTextFits); -//// } -// -// -// -// starti = (int) a.blocks[idx].start; -// -// if (idx > 0) { -// lastEnd = (int) a.blocks[idx - 1].end; -// } -// -// if (starti > regionEnd) { -// break; -// } -// -// if (starti > regionEnd) { -// if (lastEnd < regionEnd) { -// line_only = true; -// } else { -// break; -// } -// } else { -// line_only = false; -// } -// -// e = (double) a.blocks[idx].end; -// -// if (e < regionBegin) { continue; } -// s = starti - regionBegin; -// e -= regionBegin; -// s = (s < 0) ? 0 : s; -// e = (e > regionLen) ? regionLen : e; -// width = (e - s) * xScaling; -// -// if (!line_only && width > min_block_width) { -// -// -// drawBlock(plotPointedPolygons, pointLeft, edged, (float) s * xScaling, (float) e, (float) width, -// pointSlop, pH, yScaledOffset, xOffset, regionPixels, idx, nBlocks, regionLen, -// a, canvas, path, rect, faceColor, edgeColor); -// -// -// -// } -// -// } // add soft-clip blocks int start = (int) a.pos - regionBegin; int end = (int) a.reference_end - regionBegin; @@ -1139,19 +1048,20 @@ namespace Drawing { textW = fonts.textWidths[sl - 1]; if (ins.length > (uint32_t) opts.indel_length) { if (regionLen < 500000 && indelTextFits) { // line and text - drawIns(canvas, Y, p, yScaling, xOffset, yOffset, textW, theme.insS, - theme.fcIns, path, rect, pH, monitorScale); + // float yScaledOffset = (Y * yScaling) + yOffset; text_ins.emplace_back() = {SkTextBlob::MakeFromString(indelChars, fonts.overlay), - (float)(p - (textW * 0.5) + xOffset - 2), - ((Y + polygonHeight) * yScaling) + yOffset - textDrop}; + (float)(p - (textW * 0.5) + xOffset - monitorScale), + yScaledOffset + polygonHeight - textDrop, + //((Y + polygonHeight) * yScaling) + yOffset - textDrop, + yScaledOffset, + std::fmax((float)textW + monitorScale + monitorScale, ins_block_w)}; + } else { // line only - drawIns(canvas, Y, p, yScaling, xOffset, yOffset, xScaling, theme.insS, - theme.fcIns, path, rect, pH, monitorScale); + drawIns(canvas, Y, p, yScaling, xOffset, yOffset, theme.fcIns, rect, pH, ins_block_h, ins_block_w); } - } else if (regionLen < 100000 && regionLen < opts.small_indel_threshold) { // line only - drawIns(canvas, Y, p, yScaling, xOffset, yOffset, xScaling, theme.insS, - theme.fcIns, path, rect, pH, monitorScale); + } else if (regionLen < opts.small_indel_threshold) { // line only + drawIns(canvas, Y, p, yScaling, xOffset, yOffset, theme.fcIns,rect, pH, ins_block_h, ins_block_w); } } } @@ -1194,13 +1104,16 @@ namespace Drawing { // draw text last for (const auto &t : text_del) { - canvas->drawTextBlob(t.text.get(), t.x, t.y, theme.tcDel); + canvas->drawTextBlob(t.text.get(), t.x + (monitorScale * 0.5), t.y, theme.tcDel); } for (const auto &t : text_ins) { - canvas->drawTextBlob(t.text.get(), t.x, t.y, theme.tcIns); - } -// canvas->restore(); + const SkRect& bounds = t.text->bounds(); + rect.setXYWH(t.x - monitorScale, t.box_y, t.box_w, pH); + canvas->drawRect(rect, theme.fcIns); + + canvas->drawTextBlob(t.text.get(), t.x + (monitorScale * 0.5), t.y, theme.tcIns); + } // draw connecting lines between linked alignments if (linkOp > 0) { @@ -1219,15 +1132,11 @@ namespace Drawing { const Segs::Align *segA = ind[jdx]; const Segs::Align *segB = ind[jdx + 1]; -// if (segA->y == -1 || segB->y == -1 || segA->block_ends.empty() || if (segA->y == -1 || segB->y == -1 || segA->blocks.empty() || segB->blocks.empty() || -// segB->block_ends.empty() || (segA->delegate->core.tid != segB->delegate->core.tid)) { continue; } -// long cstart = std::min(segA->block_ends.front(), segB->block_ends.front()); long cstart = std::min(segA->blocks.front().end, segB->blocks.front().end); -// long cend = std::max(segA->block_starts.back(), segB->block_starts.back()); long cend = std::max(segA->blocks.back().start, segB->blocks.back().start); double x_a = ((double) cstart - (double) cl.region->start) * cl.xScaling; double x_b = ((double) cend - (double) cl.region->start) * cl.xScaling; diff --git a/src/main.cpp b/src/main.cpp index cce4651..ad85da4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -310,14 +310,14 @@ int main(int argc, char *argv[]) { vals.push_back(rg); //rg.second); i += 1; } - if (i == 0 && !have_session_file) { + if (i == 0 && !have_session_file && !std::filesystem::exists(iopts.session_file)) { std::cerr << "No genomes listed, finishing\n"; std::exit(0); } user_prompt: - if (have_session_file) { + if (have_session_file && std::filesystem::exists(iopts.session_file)) { std::cout << "\nPress ENTER to load previous session or input a genome number: " << std::flush; } else { std::cout << "\nEnter genome number: " << std::flush; @@ -330,6 +330,7 @@ int main(int argc, char *argv[]) { std::exit(0); } if (user_input.empty()) { + have_session_file = std::filesystem::exists(iopts.session_file); if (have_session_file) { use_session = true; } else { diff --git a/src/menu.cpp b/src/menu.cpp index 2849476..c39097b 100644 --- a/src/menu.cpp +++ b/src/menu.cpp @@ -305,7 +305,8 @@ namespace Menu { else if (opts.menu_level == "small_indel") { tip = "The distance in base-pairs when small indels become visible"; } else if (opts.menu_level == "snp") { tip = "The distance in base-pairs when snps become visible"; } else if (opts.menu_level == "edge_highlights") { tip = "The distance in base-pairs when edge-highlights become visible"; } - else if (opts.menu_level == "low_memory") { tip = "The distance in base-pairs when using low-memory mode (reads are not buffered)"; } + else if (opts.menu_level == "low_memory") { tip = "The distance in base-pairs when using low-memory mode (reads are not buffered in this mode)"; } + else if (opts.menu_level == "mods") { tip = "Display modified bases"; } else if (opts.menu_level == "scroll_right") { tip = "Keyboard key to use for scrolling right"; } else if (opts.menu_level == "scroll_left") { tip = "Keyboard key to use for scrolling left"; } else if (opts.menu_level == "scroll_down") { tip = "Keyboard key to use for scrolling down"; } @@ -680,12 +681,13 @@ 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"}) { + for (const auto& v : {"coverage", "log2_cov", "expand_tracks", "vcf_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"}) { option_map[v] = KeyboardKey; } + std::cout << " hi " << name << " " << value << std::endl; option_map["font"] = String; if (mt == Themes::MenuTable::GENOMES) { return Option(name, Path, value, mt); @@ -754,6 +756,7 @@ namespace Menu { else if (new_opt.name == "vcf_as_tracks") { opts.vcf_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; } else { return; } opts.myIni[new_opt.table][new_opt.name] = (v) ? "true" : "false"; } @@ -884,8 +887,6 @@ namespace Menu { return (int)(opts.log2_cov); } else if (cmd_s == "expand-tracks") { return (int)(opts.expand_tracks); -// } else if (cmd_s == "low-mem") { -// return (int)(opts.low_mem); } else if (cmd_s == "line") { return (int)drawLine; } else if (cmd_s == "insertions") { @@ -894,6 +895,8 @@ namespace Menu { return (int)(opts.edge_highlights > 0); } else if (cmd_s == "cov") { return (int)(opts.max_coverage > 0); + } else if (cmd_s == "mods") { + return (int)(opts.parse_mods); } return -1; } diff --git a/src/menu.h b/src/menu.h index 87361fd..bf661ce 100644 --- a/src/menu.h +++ b/src/menu.h @@ -49,11 +49,11 @@ namespace Menu { std::vector getCommandTip(); - constexpr std::array commandToolTip = {"ylim", "var", "tlen-y", "tags", "soft-clips", "snapshot", "sam", "remove", - "refresh", "online", "mismatches", "mate", "mate add", "log2-cov", "link", "line", "insertions", "indel-length", + 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", "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", "tags", "soft-clips", "sam", "refresh", "tlen-y"}; + 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"}; int getCommandSwitchValue(Themes::IniOptions &opts, std::string &cmd_s, bool &drawLine); diff --git a/src/plot_commands.cpp b/src/plot_commands.cpp index bf40910..753f0f0 100644 --- a/src/plot_commands.cpp +++ b/src/plot_commands.cpp @@ -35,6 +35,22 @@ #include "themes.h" +/* Notes for adding new commands: + * 1. Make a new function, accepting a pointer to a GwPlot instance and any other args + * 2. Add function to run_command_map below + * 3. Add documentation in term_out.cpp - quick help text as well as a manual + * 4. Register command in the menu functions: + * register with optionFromStr fucntion + * add to relevant functions + * getCommandSwitchValue applyBoolOption, applyKeyboardKeyOption etc + * add a tool tip in drawMenu + * if the function should be in the pop-up menu, add to commandToolTip in menu.h + * if the function should be applied straight away add to exec in menu.h + * 5. Optionally add a setting to .gw.ini + * 6. If new option should be saved in a session add it to saveCurrentSession in themes.cpp + * Loading from a session should also be detailed in loadSession in plot_manager.cpp +*/ + namespace Commands { enum Err { @@ -231,7 +247,8 @@ namespace Commands { } Err mods(Plot* p) { - p->opts.mod_threshold = (p->opts.mod_threshold == 0) ? std::stoi(p->opts.myIni["view_thresholds"]["mod"]) : 0; + p->opts.parse_mods = !(p->opts.parse_mods); +// p->opts.mod_threshold = (p->opts.mod_threshold == 0) ? std::stoi(p->opts.myIni["view_thresholds"]["mod"]) : 0; if (p->mode == Manager::Show::SINGLE) { p->processed = true; for (auto &cl : p->collections) { @@ -1112,6 +1129,7 @@ namespace Commands { Themes::GwPaint e; std::string &c = parts[1]; if (c == "bgPaint") { e = Themes::GwPaint::bgPaint; } + else if (c == "bgMenu") { e = Themes::GwPaint::bgMenu; } else if (c == "fcNormal") { e = Themes::GwPaint::fcNormal; } else if (c == "fcDel") { e = Themes::GwPaint::fcDel; } else if (c == "fcDup") { e = Themes::GwPaint::fcDup; } diff --git a/src/plot_controls.cpp b/src/plot_controls.cpp index eb0adf3..3f30e26 100644 --- a/src/plot_controls.cpp +++ b/src/plot_controls.cpp @@ -96,6 +96,10 @@ namespace Manager { return key; } if (key == GLFW_KEY_TAB && !captureText) { + if (variantTracks.empty()) { + return GLFW_KEY_UNKNOWN; + } + currentVarTrack = &variantTracks[variantFileSelection]; if (currentVarTrack == nullptr) { return key; @@ -1908,16 +1912,16 @@ namespace Manager { } } else if (std::fabs(xoffset) > std::fabs(yoffset) && 0.1 < std::fabs(xoffset)) { if (xoffset < 0) { - keyPress(opts.scroll_right, 0, GLFW_PRESS, 0); - } else { keyPress(opts.scroll_left, 0, GLFW_PRESS, 0); + } else { + keyPress(opts.scroll_right, 0, GLFW_PRESS, 0); } } } else if (std::fabs(yoffset) > std::fabs(xoffset) && 0.1 < std::fabs(yoffset)) { if (yoffset < 0) { - keyPress(opts.scroll_right, 0, GLFW_PRESS, 0); - } else { keyPress(opts.scroll_left, 0, GLFW_PRESS, 0); + } else { + keyPress(opts.scroll_right, 0, GLFW_PRESS, 0); } } } diff --git a/src/plot_manager.cpp b/src/plot_manager.cpp index 377393b..c161c3e 100644 --- a/src/plot_manager.cpp +++ b/src/plot_manager.cpp @@ -388,7 +388,8 @@ namespace Manager { } void GwPlot::addIdeogram(std::string path) { - Themes::readIdeogramFile(path, ideogram); + ideogram_path = path; + Themes::readIdeogramFile(path, ideogram, opts.theme); } void GwPlot::addFilter(std::string &filter_str) { @@ -443,6 +444,9 @@ 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 (opts.seshIni["data"].has("mode")) { mode = (opts.seshIni["data"]["mode"] == "tiled") ? Show::TILED : Show::SINGLE; } @@ -531,7 +535,7 @@ namespace Manager { } int xpos, ypos; glfwGetWindowPos(window, &xpos, &ypos); - opts.saveCurrentSession(reference, bam_paths, track_paths, regions, variant_paths_info, commandsApplied, + opts.saveCurrentSession(reference, ideogram_path, bam_paths, track_paths, regions, variant_paths_info, commandsApplied, "", mode, xpos, ypos, monitorScale); } @@ -729,7 +733,7 @@ namespace Manager { collections[idx].mmVector.clear(); } } - if (reg->end - reg->start < opts.low_memory) { + 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, opts.parse_mods); int maxY = Segs::findY(collections[idx], collections[idx].readQueue, opts.link_op, opts, reg, false); if (maxY > samMaxY) { @@ -871,7 +875,7 @@ namespace Manager { for (auto &cl: collections) { canvasR->save(); - std::cout << cl.skipDrawingCoverage << " " << cl.skipDrawingReads << std::endl; + // Copy some of the image from the last frame if ((cl.skipDrawingCoverage || cl.skipDrawingReads) && !imageCacheQueue.empty()) { if (cl.skipDrawingCoverage) { @@ -895,9 +899,9 @@ namespace Manager { if (!cl.skipDrawingReads) { - if (cl.regionLen >= opts.low_memory && !bams.empty()) { // low memory mode will be used + if (cl.regionLen >= opts.low_memory && !bams.empty() && opts.link_op == 0) { // low memory mode will be used cl.clear(); - std::cout << " iter draw\n"; +// std::cout << " iter draw\n"; if (opts.threads == 1) { HGW::iterDraw(cl, bams[cl.bamIdx], headers[cl.bamIdx], indexes[cl.bamIdx], ®ions[cl.regionIdx], (bool) opts.max_coverage, @@ -926,6 +930,9 @@ namespace Manager { Drawing::drawBorders(opts, fb_width, fb_height, canvasR, regions.size(), bams.size(), trackY, covY, (int)tracks.size(), totalTabixY, refSpace, gap); Drawing::drawTracks(opts, fb_width, fb_height, canvasR, totalTabixY, tabixY, tracks, regions, fonts, gap, monitorScale); Drawing::drawChromLocation(opts, regions, ideogram, canvasR, fai, fb_width, fb_height, monitorScale); +// if (mode == Show::SINGLE) { +// drawCursorPosOnRefSlider(canvas); +// } } @@ -996,9 +1003,13 @@ namespace Manager { } canvas->drawImage(imageCacheQueue.back().second, 0, 0); } + + // slider overlay if (mode == Show::SINGLE) { drawCursorPosOnRefSlider(canvas); } + + // draw menu if (mode == Show::SETTINGS) { if (!imageCacheQueue.empty()) { while (imageCacheQueue.front().first != frameId) { @@ -1010,6 +1021,8 @@ namespace Manager { canvas->drawPaint(bg); } Menu::drawMenu(canvas, opts, fonts, monitorScale, fb_width, fb_height, inputText, charIndex); + + // box for change in region selection using keyboard } else if (regionSelectionTriggered && regions.size() > 1) { SkRect rect{}; float step = (float)fb_width / (float)regions.size(); @@ -1027,6 +1040,7 @@ namespace Manager { canvas->drawRoundRect(rect, 5 * monitorScale, 5 * monitorScale, box); } + // icon information for vcf-file if (mode == Show::TILED && variantTracks.size() > 1) { float tile_box_w = std::fmin(100 * monitorScale, (fb_width - (variantTracks.size() * gap + 1)) / variantTracks.size()); SkPaint box{}; @@ -1060,7 +1074,7 @@ namespace Manager { yposm *= (float) fb_height / (float) windowH; } - + // Text overlay for vcf file info bool variantFile_info = mode == TILED && variantTracks.size() > 1 && yposm < fb_height * 0.02; if (variantFile_info) { float tile_box_w = std::fmin(100 * monitorScale, (fb_width - (variantTracks.size() * gap + 1)) / variantTracks.size()); @@ -1081,6 +1095,7 @@ namespace Manager { } } + // command box if (captureText && !opts.editing_underway) { fonts.setFontSize(yScaling, monitorScale); SkRect rect{}; @@ -1088,12 +1103,7 @@ namespace Manager { float x = 50; float w = fb_width - 100; - SkPaint bg; - if (opts.theme_str != "igv") { - bg.setARGB(255, 15, 15, 25); - } else { - bg.setARGB(255, 240, 240, 240); - } + SkPaint bg = opts.theme.bgMenu; if (x < w) { float y = fb_height - (fb_height * 0.025); @@ -1112,26 +1122,28 @@ namespace Manager { rect.setXYWH(0, yy, fb_width, fb_height); - canvas->drawRect(rect, bg); + canvas->drawRoundRect(rect, 5 * monitorScale, 5 * monitorScale, bg); + float pad = fonts.overlayHeight * 0.3; + // Cursor and text SkPath path; - path.moveTo(x + 14 + to_cursor_width, yy + (fonts.overlayHeight * 0.3)); - path.lineTo(x + 14 + to_cursor_width, yy + fonts.overlayHeight * 1.5); + path.moveTo(x + 14 + to_cursor_width, yy + (fonts.overlayHeight * 0.3) + pad + pad); + path.lineTo(x + 14 + to_cursor_width, yy + (fonts.overlayHeight * 1.5) + pad + pad); canvas->drawPath(path, opts.theme.lcBright); if (!inputText.empty()) { sk_sp blob = SkTextBlob::MakeFromString(inputText.c_str(), fonts.overlay); - canvas->drawTextBlob(blob, x + 14, yy + (fonts.overlayHeight * 1.3), opts.theme.tcDel); + canvas->drawTextBlob(blob, x + 14, yy + (fonts.overlayHeight * 1.3) + pad + pad, opts.theme.tcDel); } if (mode != SETTINGS && (commandToolTipIndex != -1 || !inputText.empty())) { - float pad = fonts.overlayHeight * 0.3; + if (inputText.empty() && yy - (Menu::commandToolTip.size() * (fonts.overlayHeight+ pad)) < covY) { sk_sp blob = SkTextBlob::MakeFromString(Menu::commandToolTip[commandToolTipIndex], fonts.overlay); SkPaint grey; grey.setColor(SK_ColorGRAY); canvas->drawTextBlob(blob, x + 14 + fonts.overlayWidth + fonts.overlayWidth, yy + (fonts.overlayHeight * 1.3), grey); } + yy += pad; - yy -= pad + pad; SkPaint tip_paint = opts.theme.lcBright; tip_paint.setAntiAlias(true); float n = 0; @@ -1150,11 +1162,10 @@ namespace Manager { float step = fonts.overlayHeight + pad; float top = yy - (n * step); rect.setXYWH(0, top, x + fonts.overlayWidth * 18, (n * step) + pad + pad); - canvas->drawRect(rect, bg); + canvas->drawRoundRect(rect, 10, 10, bg); } - for (const auto &cmd : Menu::commandToolTip) { std::string cmd_s = cmd; if (!inputText.empty() && !Utils::startsWith(cmd_s, inputText)) { @@ -1163,7 +1174,7 @@ namespace Manager { if (cmd_s == inputText) { break; } - rect.setXYWH(x, yy - fonts.overlayHeight - pad, fonts.overlayWidth * 18, fonts.overlayHeight + pad + pad); +// rect.setXYWH(x, yy - fonts.overlayHeight - pad, fonts.overlayWidth * 18, fonts.overlayHeight + pad + pad); // canvas->drawRoundRect(rect, 5 * monitorScale, 5 * monitorScale, opts.theme.bgPaint); sk_sp blob = SkTextBlob::MakeFromString(cmd, fonts.overlay); canvas->drawTextBlob(blob, x + (fonts.overlayWidth * 3), yy, opts.theme.tcDel); @@ -1189,6 +1200,7 @@ namespace Manager { } } + // cog and bubble float half_h = (float)fb_height / 2; bool tool_popup = (xposm > 0 && xposm <= 60 && yposm >= half_h - 150 && yposm <= half_h + 150 && (xDrag == -1000000 || xDrag == 0) && (yDrag == -1000000 || yDrag == 0)); if (tool_popup) { diff --git a/src/plot_manager.h b/src/plot_manager.h index 491e6cd..485d710 100644 --- a/src/plot_manager.h +++ b/src/plot_manager.h @@ -94,6 +94,8 @@ namespace Manager { std::string reference; + std::string ideogram_path; + std::string inputText; std::string target_qname; diff --git a/src/themes.cpp b/src/themes.cpp index 8d49807..6d65955 100644 --- a/src/themes.cpp +++ b/src/themes.cpp @@ -136,7 +136,6 @@ namespace Themes { lcLabel.setStrokeWidth(1); lcLabel.setAntiAlias(true); -// lcBright.setStyle(SkPaint::kStrokeAndFill_Style); lcBright.setStyle(SkPaint::kStroke_Style); lcBright.setStrokeWidth(2); lcBright.setAntiAlias(true); @@ -144,10 +143,6 @@ namespace Themes { insF = fcIns; insF.setStyle(SkPaint::kFill_Style); - insS = fcIns; - insS.setStyle(SkPaint::kStroke_Style); - insS.setStrokeWidth(4); - fcMarkers.setStyle(SkPaint::kStrokeAndFill_Style); fcMarkers.setAntiAlias(true); fcMarkers.setStrokeMiter(0.1); @@ -239,6 +234,7 @@ namespace Themes { fcCoverage.setARGB(255, 195, 195, 195); fcTrack.setARGB(200, 0, 0, 0); bgPaint.setARGB(255, 255, 255, 255); + bgMenu.setARGB(255, 250, 250, 250); fcNormal.setARGB(255, 202, 202, 202); fcDel.setARGB(255, 225, 19, 67); fcDup.setARGB(255, 0, 54, 205); @@ -247,7 +243,6 @@ namespace Themes { fcTra.setARGB(255, 255, 105, 180); fcSoftClip.setARGB(255, 0, 128, 128); fcA.setARGB(255, 109, 230, 64); -// fcT.setARGB(255, 215, 0, 0); fcT.setARGB(255, 255, 0, 107); fcC.setARGB(255, 66, 127, 255); fcG.setARGB(255, 235, 150, 23); @@ -274,7 +269,7 @@ namespace Themes { fcCoverage.setARGB(255, 95, 95, 105); fcTrack.setARGB(200, 227, 232, 255); bgPaint.setARGB(255, 10, 10, 20); -// bgPaint.setARGB(255, 0, 0, 0); + bgMenu.setARGB(255, 20, 20, 30); fcNormal.setARGB(255, 90, 90, 95); fcDel.setARGB(255, 185, 25, 25); fcDup.setARGB(255, 24, 100, 198); @@ -283,8 +278,6 @@ namespace Themes { fcTra.setARGB(255, 225, 185, 185); fcSoftClip.setARGB(255, 0, 128, 128); fcA.setARGB(255, 106, 186, 79); -// fcT.setARGB(255, 201, 49, 24); -// fcA.setARGB(255, 105, 213, 92); fcT.setARGB(255, 232, 55, 99); fcC.setARGB(255, 77, 125, 245); fcG.setARGB(255, 226, 132, 19); @@ -311,9 +304,9 @@ namespace Themes { fcCoverage.setARGB(255, 103, 102, 109); fcTrack.setARGB(200, 227, 232, 255); bgPaint.setARGB(255, 45, 45, 48); + bgMenu.setARGB(255, 0, 0, 0); fcNormal.setARGB(255, 93, 92, 99); fcDel.setARGB(255, 185, 25, 25); -// fcIns.setARGB(255, 225, 235, 245); fcIns.setARGB(255, 128, 91, 240); fcDup.setARGB(255, 24, 100, 198); fcInvF.setARGB(255, 49, 167, 118); @@ -455,8 +448,8 @@ namespace Themes { if (myIni["general"].has("sv_arcs")) { sv_arcs = myIni["general"]["sv_arcs"] == "true"; } - if (myIni["general"].has("show_mods")) { - parse_mods = myIni["general"]["show_mods"] == "true"; + if (myIni["general"].has("mods")) { + parse_mods = myIni["general"]["mods"] == "true"; } if (myIni["general"].has("session_file")) { session_file = myIni["general"]["session_file"]; @@ -684,15 +677,17 @@ namespace Themes { void IniOptions::saveIniChanges() { mINI::INIFile file(ini_path); - file.write(myIni); + file.generate(myIni); } - void IniOptions::saveCurrentSession(std::string& genome_path, std::vector& bam_paths, + void IniOptions::saveCurrentSession(std::string& genome_path, std::string& ideogram_path, std::vector& bam_paths, std::vector& track_paths, std::vector& regions, std::vector>& variant_paths_info, std::vector& commands, std::string output_session, int mode, int window_x_pos, int window_y_pos, float monitorScale) { + std::cout << output_session << " , " << session_file << " saving session \n"; if (output_session.empty()) { + if (session_file.empty()) { // fill new session std::filesystem::path gwini(ini_path); std::filesystem::path sesh(".gw_session.ini"); @@ -703,9 +698,13 @@ 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; + if (!ideogram_path.empty()) { + seshIni["data"]["ideogram_path"] = ideogram_path; + } int count = 0; for (const auto& item : bam_paths) { seshIni["data"]["bam" + std::to_string(count)] = item; @@ -713,6 +712,7 @@ namespace Themes { } count = 0; for (const auto& item : track_paths) { + std::cout << " track " << std::endl; seshIni["data"]["track" + std::to_string(count)] = item; count += 1; } @@ -737,7 +737,7 @@ namespace Themes { seshIni["show"]["window_position"] = std::to_string(window_x_pos) + "x" + std::to_string(window_y_pos); count = 0; size_t last_refresh = 0; - std::vector keep = {"egdes", "expand-tracks", "soft-clips", "sc", "mm", "mismatches", "ins", "insertions"}; + std::vector keep = {"egdes", "expand-tracks", "soft-clips", "sc", "mm", "mismatches", "ins", "insertions", "mods"}; for (auto& item: commands) { for (const auto& k: keep) { if (Utils::startsWith(item, k)) { @@ -767,6 +767,7 @@ namespace Themes { sub["coverage"] = (max_coverage) ? "true" : "false"; sub["log2_cov"] = (log2_cov) ? "true" : "false"; sub["expand_tracks"] = (expand_tracks) ? "true" : "false"; + sub["mods"] = (parse_mods) ? "true" : "false"; sub["link"] = link; sub["split_view_size"] = std::to_string(split_view_size); sub["pad"] = std::to_string(pad); @@ -873,13 +874,16 @@ namespace Themes { std::cout << "};\n\n"; } - void readIdeogramFile(std::string file_path, std::unordered_map> &ideogram) { + void readIdeogramFile(std::string file_path, std::unordered_map> &ideogram, + Themes::BaseTheme &theme) { std::ifstream band_file(file_path); if (!band_file) { throw std::runtime_error("Failed to open input files"); } std::unordered_map custom; std::string line, token, chrom, name, property; + int acen = theme.fcT.getColor(); + int gvar = theme.fcC.getColor(); while (std::getline(band_file, line)) { std::istringstream iss(line); if (line[0] == '#') { @@ -916,9 +920,9 @@ namespace Themes { } else if (property == "gpos100") { ideogram[chrom].emplace_back() = {start, end, 255, 60, 60, 60, {}, name}; } else if (property == "acen") { - ideogram[chrom].emplace_back() = {start, end, 255, 220, 10, 10, {}, name}; + ideogram[chrom].emplace_back() = {start, end, 255, SkColorGetR(acen), SkColorGetG(acen), SkColorGetB(acen), {}, name}; } else if (property == "gvar") { - ideogram[chrom].emplace_back() = {start, end, 255, 10, 10, 220, {}, name}; + ideogram[chrom].emplace_back() = {start, end, 255, SkColorGetR(gvar), SkColorGetG(gvar), SkColorGetB(gvar), {}, name}; } else if (custom.find(name) != custom.end()) { Band cust = custom[name]; cust.start = start; @@ -933,8 +937,7 @@ namespace Themes { } catch (...) { } - } - else { + } else { ideogram[chrom].emplace_back() = {start, end, 255, 85, 171, 159, {}, name}; } } else { diff --git a/src/themes.h b/src/themes.h index af29ffd..1225711 100644 --- a/src/themes.h +++ b/src/themes.h @@ -68,7 +68,7 @@ namespace Themes { if an item has 0 at the end this is the color when mapq == 0 */ enum GwPaint { - bgPaint, fcNormal, fcDel, fcDup, fcInvF, fcInvR, fcTra, fcIns, fcSoftClip, + bgPaint, bgMenu, fcNormal, fcDel, fcDup, fcInvF, fcInvR, fcTra, fcIns, fcSoftClip, fcA, fcT, fcC, fcG, fcN, fcCoverage, fcTrack, fcNormal0, fcDel0, fcDup0, fcInvF0, fcInvR0, fcTra0, fcSoftClip0, fcBigWig, fcRoi, mate_fc, mate_fc0, ecMateUnmapped, ecSplit, ecSelected, lcJoins, lcCoverage, lcLightJoins, lcLabel, lcBright, tcDel, tcIns, tcLabels, tcBackground, @@ -82,7 +82,7 @@ namespace Themes { std::string name; // face colours - SkPaint bgPaint, fcNormal, fcDel, fcDup, fcInvF, fcInvR, fcTra, fcIns, fcSoftClip, \ + SkPaint bgPaint, bgMenu, fcNormal, fcDel, fcDup, fcInvF, fcInvR, fcTra, fcIns, fcSoftClip, \ fcA, fcT, fcC, fcG, fcN, fcCoverage, fcTrack, fcRoi; SkPaint fcNormal0, fcDel0, fcDup0, fcInvF0, fcInvR0, fcTra0, fcSoftClip0, fcBigWig; @@ -96,7 +96,7 @@ namespace Themes { float lwMateUnmapped, lwSplit, lwCoverage; // line colours and Insertion paint - SkPaint lcJoins, lcCoverage, lcLightJoins, insF, insS, lcLabel, lcBright; + SkPaint lcJoins, lcCoverage, lcLightJoins, insF, lcLabel, lcBright; // text colours SkPaint tcDel, tcIns, tcLabels, tcBackground; @@ -175,7 +175,7 @@ namespace Themes { void getOptionsFromSessionIni(mINI::INIStructure& seshIni); void saveIniChanges(); void setTheme(std::string &theme_str); - void saveCurrentSession(std::string& genome_path, std::vector& bam_paths, + void saveCurrentSession(std::string& genome_path, std::string& ideogram_path, std::vector& bam_paths, std::vector& track_paths, std::vector& regions, std::vector>& variant_paths_info, std::vector& commands, std::string output_session, @@ -206,6 +206,7 @@ namespace Themes { std::string name; }; - void readIdeogramFile(std::string file_path, std::unordered_map> &ideogram); + void readIdeogramFile(std::string file_path, std::unordered_map> &ideogram, + Themes::BaseTheme &theme); } From 9f9e5c0726ceba0d9ca8a055665cc2d15ed310ba Mon Sep 17 00:00:00 2001 From: kcleal Date: Sun, 7 Jul 2024 12:02:38 +0100 Subject: [PATCH 46/51] Mods+ideogram work with session file. Mods qualt threshold option. Read length to terminal. No crash when chrom not in ref at startup --- .gw.ini | 1 + src/hts_funcs.cpp | 41 ++++++++++++------ src/hts_funcs.h | 2 +- src/main.cpp | 11 ++--- src/menu.cpp | 7 +-- src/plot_commands.cpp | 4 +- src/plot_controls.cpp | 3 +- src/plot_manager.cpp | 9 ++-- src/segments.cpp | 22 ++++------ src/segments.h | 4 +- src/term_out.cpp | 1 + src/themes.cpp | 6 ++- src/themes.h | 2 +- ...:13829780-13866370_mod_call_sample.bam.bai | Bin 6864 -> 0 bytes ...le.bam => chr20_13822936-13833912.mod.bam} | Bin 107862 -> 107859 bytes test/chr20_13822936-13833912.mod.bam.bai | Bin 0 -> 6864 bytes 16 files changed, 65 insertions(+), 48 deletions(-) delete mode 100644 test/chr20:13829780-13866370_mod_call_sample.bam.bai rename test/{chr20:13829780-13866370_mod_call_sample.bam => chr20_13822936-13833912.mod.bam} (95%) create mode 100644 test/chr20_13822936-13833912.mod.bam.bai diff --git a/.gw.ini b/.gw.ini index 88bc204..a2e1ae8 100644 --- a/.gw.ini +++ b/.gw.ini @@ -38,6 +38,7 @@ font=Menlo font_size=14 sv_arcs=true mods=false +mods_qual_threshold=50 session_file= [view_thresholds] diff --git a/src/hts_funcs.cpp b/src/hts_funcs.cpp index c56e38e..02af9cf 100644 --- a/src/hts_funcs.cpp +++ b/src/hts_funcs.cpp @@ -160,7 +160,8 @@ namespace HGW { void collectReadsAndCoverage(Segs::ReadCollection &col, htsFile *b, sam_hdr_t *hdr_ptr, hts_idx_t *index, int threads, Utils::Region *region, bool coverage, std::vector &filters, BS::thread_pool &pool, - const bool parse_mods) { + const int parse_mods_threshold) { + bam1_t *src; hts_itr_t *iter_q; int tid = sam_hdr_name2tid(hdr_ptr, region->chrom.c_str()); @@ -175,7 +176,8 @@ namespace HGW { iter_q = sam_itr_queryi(index, tid, region->start, region->end); if (iter_q == nullptr) { std::cerr << "\nError: Null iterator when trying to fetch from HTS file in collectReadsAndCoverage " << region->chrom << " " << region->start << " " << region->end << std::endl; - throw std::runtime_error(""); + return; +// throw std::runtime_error(""); } while (sam_itr_next(b, iter_q, readQueue.back().delegate) >= 0) { @@ -191,7 +193,7 @@ namespace HGW { readQueue.pop_back(); } - Segs::init_parallel(readQueue, threads, pool, parse_mods); + Segs::init_parallel(readQueue, threads, pool, parse_mods_threshold); if (!filters.empty()) { applyFilters(filters, readQueue, hdr_ptr, col.bamIdx, col.regionIdx); @@ -368,9 +370,12 @@ namespace HGW { iter_q = sam_itr_queryi(index, tid, region->start, region->end); if (iter_q == nullptr) { std::cerr << "\nError: Null iterator when trying to fetch from HTS file in collectReadsAndCoverage " << region->chrom << " " << region->start << " " << region->end << std::endl; - throw std::runtime_error(""); +// throw std::runtime_error(""); + return; } bool filter = !filters.empty(); + const int parse_mods_threshold = (opts.parse_mods) ? 50 : 0; + int j = 0; while (sam_itr_next(b, iter_q, readQueue[j].delegate) >= 0) { src = readQueue[j].delegate; @@ -381,7 +386,7 @@ namespace HGW { if (j < BATCH) { continue; } - Segs::init_parallel(readQueue, threads, pool, opts.parse_mods); + Segs::init_parallel(readQueue, threads, pool, parse_mods_threshold); if (filter) { applyFilters_noDelete(filters, readQueue, hdr_ptr, col.bamIdx, col.regionIdx); } @@ -405,7 +410,7 @@ namespace HGW { if (j < BATCH) { readQueue.erase(readQueue.begin() + j, readQueue.end()); if (!readQueue.empty()) { - Segs::init_parallel(readQueue, threads, pool, opts.parse_mods); + Segs::init_parallel(readQueue, threads, pool, parse_mods_threshold); if (!filters.empty()) { applyFilters_noDelete(filters, readQueue, hdr_ptr, col.bamIdx, col.regionIdx); } @@ -432,9 +437,7 @@ namespace HGW { std::vector &filters, Themes::IniOptions &opts, SkCanvas *canvas, float trackY, float yScaling, Themes::Fonts &fonts, float refSpace, float pointSlop, float textDrop, float pH, float monitorScale) { -// if (region->end == 0) { -// return; -// } + bam1_t *src; hts_itr_t *iter_q; int tid = sam_hdr_name2tid(hdr_ptr, region->chrom.c_str()); @@ -449,15 +452,18 @@ namespace HGW { iter_q = sam_itr_queryi(index, tid, region->start, region->end); if (iter_q == nullptr) { std::cerr << "\nError: Null iterator when trying to fetch from HTS file in collectReadsAndCoverage " << region->chrom << " " << region->start << " " << region->end << std::endl; - throw std::runtime_error(""); +// throw std::runtime_error(""); + return; } bool filter = !filters.empty(); + const int parse_mods_threshold = (opts.parse_mods) ? 50 : 0; + while (sam_itr_next(b, iter_q, readQueue.back().delegate) >= 0) { src = readQueue.back().delegate; if (src->core.flag & 4 || src->core.n_cigar == 0) { continue; } - Segs::align_init(&readQueue.back(), opts.parse_mods); + Segs::align_init(&readQueue.back(), parse_mods_threshold); if (filter) { applyFilters_noDelete(filters, readQueue, hdr_ptr, col.bamIdx, col.regionIdx); if (readQueue.back().y == -2) { @@ -548,7 +554,12 @@ namespace HGW { Utils::Region *region = col.region; bool tlen_y = opts.tlen_yscale; int tid = sam_hdr_name2tid(hdr_ptr, region->chrom.c_str()); + if (tid < 0) { + return; + } int lastPos; + const int parse_mods_threshold = (opts.parse_mods) ? 50 : 0; + if (!readQueue.empty()) { if (left) { lastPos = readQueue.front().pos; // + 1; @@ -598,7 +609,8 @@ namespace HGW { iter_q = sam_itr_queryi(index, tid, begin, end_r); if (iter_q == nullptr) { std::cerr << "\nError: Null iterator when trying to fetch from HTS file in appendReadsAndCoverage (left) " << region->chrom << " " << region->start<< " " << end_r << " " << region->end << std::endl; - throw std::runtime_error(""); +// throw std::runtime_error(""); + return; } newReads.emplace_back(Segs::Align(bam_init1())); @@ -647,7 +659,8 @@ namespace HGW { } if (iter_q == nullptr) { std::cerr << "\nError: Null iterator when trying to fetch from HTS file in appendReadsAndCoverage (!left) " << region->chrom << " " << lastPos << " " << region->end << std::endl; - throw std::runtime_error(""); +// throw std::runtime_error(""); + return; } newReads.emplace_back(Segs::Align(bam_init1())); @@ -672,7 +685,7 @@ namespace HGW { if (!filters.empty()) { applyFilters(filters, newReads, hdr_ptr, col.bamIdx, col.regionIdx); } - Segs::init_parallel(newReads, opts.threads, pool, opts.parse_mods); + Segs::init_parallel(newReads, opts.threads, pool, parse_mods_threshold); bool findYall = false; if (col.vScroll == 0 && opts.link_op == 0) { // only new reads need findY, otherwise, reset all below int maxY = Segs::findY(col, newReads, opts.link_op, opts, region, left); diff --git a/src/hts_funcs.h b/src/hts_funcs.h index c758de6..8daf2d7 100644 --- a/src/hts_funcs.h +++ b/src/hts_funcs.h @@ -92,7 +92,7 @@ namespace HGW { hts_idx_t *index, int threads, Utils::Region *region, bool coverage, std::vector &filters, BS::thread_pool &pool, - const bool parse_mods); + const int parse_mods); void iterDrawParallel(Segs::ReadCollection &col, htsFile *b, diff --git a/src/main.cpp b/src/main.cpp index ad85da4..7dd63fb 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -183,9 +183,9 @@ int main(int argc, char *argv[]) { program.add_argument("--no-soft-clips") .default_value(false).implicit_value(true) .help("Soft-clips are not shown"); - program.add_argument("--no-mods") + program.add_argument("--mods") .default_value(false).implicit_value(true) - .help("Base modifications are not shown"); + .help("Base modifications are shown"); program.add_argument("--low-mem") .default_value(false).implicit_value(true) .help("Reduce memory usage by discarding quality values"); @@ -472,6 +472,9 @@ int main(int argc, char *argv[]) { if (program.is_used("--tlen-y")) { iopts.tlen_yscale = true; } + if (program.is_used("--mods")) { + iopts.parse_mods = true; + } if (program.is_used("--split-view-size")) { iopts.split_view_size = program.get("--split-view-size"); } @@ -490,9 +493,7 @@ int main(int argc, char *argv[]) { if (program.is_used("--no-soft-clips")) { iopts.soft_clip_threshold = 0; } - if (program.is_used("--no-mods")) { - iopts.parse_mods = 0; - } + if (program.is_used("--start-index")) { iopts.start_index = program.get("--start-index"); } diff --git a/src/menu.cpp b/src/menu.cpp index c39097b..8204fbf 100644 --- a/src/menu.cpp +++ b/src/menu.cpp @@ -307,6 +307,7 @@ namespace Menu { else if (opts.menu_level == "edge_highlights") { tip = "The distance in base-pairs when edge-highlights become visible"; } else if (opts.menu_level == "low_memory") { tip = "The distance in base-pairs when using low-memory mode (reads are not buffered in this mode)"; } else if (opts.menu_level == "mods") { tip = "Display modified bases"; } + else if (opts.menu_level == "mods_qual_threshold") { tip = "Threshold for displaying modified bases [0-255]"; } else if (opts.menu_level == "scroll_right") { tip = "Keyboard key to use for scrolling right"; } else if (opts.menu_level == "scroll_left") { tip = "Keyboard key to use for scrolling left"; } else if (opts.menu_level == "scroll_down") { tip = "Keyboard key to use for scrolling down"; } @@ -675,7 +676,7 @@ namespace Menu { Option optionFromStr(std::string &name, Themes::MenuTable mt, std::string &value) { std::unordered_map option_map; - for (const auto& v : {"indel_length", "ylim", "split_view_size", "threads", "pad", "soft_clip", "small_indel", "snp", "edge_highlights", "font_size", "variant_distance"}) { + for (const auto& v : {"indel_length", "ylim", "split_view_size", "threads", "pad", "soft_clip", "small_indel", "snp", "edge_highlights", "font_size", "variant_distance", "mods_qual_threshold"}) { option_map[v] = Int; } for (const auto& v : {"scroll_speed", "tabix_track_height"}) { @@ -687,7 +688,6 @@ namespace Menu { 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"}) { option_map[v] = KeyboardKey; } - std::cout << " hi " << name << " " << value << std::endl; option_map["font"] = String; if (mt == Themes::MenuTable::GENOMES) { return Option(name, Path, value, mt); @@ -708,7 +708,7 @@ namespace Menu { std::string::const_iterator it = new_opt.value.begin(); while (it != new_opt.value.end() && std::isdigit(*it)) ++it; if (it != new_opt.value.end()) { - std::cerr << termcolor::red << "Error:" << termcolor::reset << " expected an integer number, instead of " << new_opt.value << std::endl; + std::cerr << termcolor::red << "Error:" << termcolor::reset << " expected a positive integer number, instead of " << new_opt.value << std::endl; } else { int v = std::stoi(new_opt.value); if (new_opt.name == "indel_length") { opts.indel_length = std::max(1, v); } @@ -722,6 +722,7 @@ namespace Menu { else if (new_opt.name == "edge_highlights") { opts.edge_highlights = std::max(1, v); } else if (new_opt.name == "font_size") { opts.font_size = std::max(1, v); } else if (new_opt.name == "variant_distance") { opts.variant_distance = std::max(1, v); } + else if (new_opt.name == "mods_qual_threshold") { opts.mods_qual_threshold = std::min(std::max(0, v), 255); new_opt.value = std::to_string(opts.mods_qual_threshold); } else { return; } opts.myIni[new_opt.table][new_opt.name] = new_opt.value; } diff --git a/src/plot_commands.cpp b/src/plot_commands.cpp index 753f0f0..e54ccf6 100644 --- a/src/plot_commands.cpp +++ b/src/plot_commands.cpp @@ -250,7 +250,7 @@ namespace Commands { p->opts.parse_mods = !(p->opts.parse_mods); // p->opts.mod_threshold = (p->opts.mod_threshold == 0) ? std::stoi(p->opts.myIni["view_thresholds"]["mod"]) : 0; if (p->mode == Manager::Show::SINGLE) { - p->processed = true; + p->processed = false; for (auto &cl : p->collections) { cl.skipDrawingReads = false; cl.skipDrawingCoverage = false; @@ -953,7 +953,7 @@ namespace Commands { bam1_t* a = bam_init1(); if (sam_itr_next(file_ptrs[i], region_iters[i], a) >= 0) { Segs::Align alignment = Segs::Align(a); - Segs::align_init(&alignment, p->opts.parse_mods); + Segs::align_init(&alignment, 0); // no need to parse mods here pq.push({std::move(alignment), file_ptrs[i], region_iters[i], i}); } else { bam_destroy1(a); diff --git a/src/plot_controls.cpp b/src/plot_controls.cpp index 3f30e26..e966312 100644 --- a/src/plot_controls.cpp +++ b/src/plot_controls.cpp @@ -851,7 +851,8 @@ namespace Manager { if (cl.regionIdx == regionSelection) { if (!bams.empty() && cl.regionLen >= opts.low_memory && region.end - region.start < opts.low_memory) { cl.clear(); - HGW::collectReadsAndCoverage(cl, bams[cl.bamIdx], headers[cl.bamIdx], indexes[cl.bamIdx], opts.threads, ®ion, (bool)opts.max_coverage, filters, pool, opts.parse_mods); + const int parse_mods_threshold = (opts.parse_mods) ? opts.mods_qual_threshold: 0; + HGW::collectReadsAndCoverage(cl, bams[cl.bamIdx], headers[cl.bamIdx], indexes[cl.bamIdx], opts.threads, ®ion, (bool)opts.max_coverage, filters, pool, parse_mods_threshold); int maxY = Segs::findY(cl, cl.readQueue, opts.link_op, opts, ®ion, false); if (maxY > samMaxY) { samMaxY = maxY; diff --git a/src/plot_manager.cpp b/src/plot_manager.cpp index c161c3e..385c242 100644 --- a/src/plot_manager.cpp +++ b/src/plot_manager.cpp @@ -435,6 +435,7 @@ namespace Manager { } } + // read tracks and data from session, other options already read IniOptions::getOptionsFromSessionIni void GwPlot::loadSession() { mINI::INIFile file(opts.session_file); file.read(opts.seshIni); @@ -684,6 +685,7 @@ 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; if (!processed) { int idx = 0; if (collections.size() != bams.size() * regions.size()) { @@ -734,7 +736,7 @@ 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, opts.parse_mods); + 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; @@ -857,7 +859,6 @@ namespace Manager { void GwPlot::drawScreen(SkCanvas* canvas, GrDirectContext* sContext, SkSurface *sSurface) { // std::chrono::high_resolution_clock::time_point initial = std::chrono::high_resolution_clock::now(); - SkCanvas *canvasR = rasterCanvas; canvas->drawPaint(opts.theme.bgPaint); @@ -873,7 +874,6 @@ namespace Manager { setScaling(); for (auto &cl: collections) { - canvasR->save(); // Copy some of the image from the last frame @@ -898,7 +898,6 @@ namespace Manager { if (!cl.skipDrawingReads) { - if (cl.regionLen >= opts.low_memory && !bams.empty() && opts.link_op == 0) { // low memory mode will be used cl.clear(); // std::cout << " iter draw\n"; @@ -920,8 +919,8 @@ namespace Manager { } } canvasR->restore(); - } + // std::cout << " done draw\n"; if (opts.max_coverage) { Drawing::drawCoverage(opts, collections, canvasR, fonts, covY, refSpace); diff --git a/src/segments.cpp b/src/segments.cpp index 2adef27..fc85e9a 100644 --- a/src/segments.cpp +++ b/src/segments.cpp @@ -237,7 +237,8 @@ namespace Segs { // } - /* + /* NOTE PARSING MODS SECTION BELOW IS FROM HTS-LIB + * * Count frequency of A, C, G, T and N canonical bases in the sequence */ #define MAX_BASE_MOD 256 @@ -687,7 +688,7 @@ namespace Segs { u, u, u, u, u, u, u, u, INV_F}; - void align_init(Align *self, const bool parse_mods) { + void align_init(Align *self, const int parse_mods_threshold) { // auto start = std::chrono::high_resolution_clock::now(); bam1_t *src = self->delegate; @@ -758,7 +759,7 @@ namespace Segs { self->has_SA = false; } - if (parse_mods) { + if (parse_mods_threshold > 0) { hts_base_mod_state* mod_state = new hts_base_mod_state; int res = bam_parse_basemod_gw(src, mod_state, 0); if (res >= 0) { @@ -770,9 +771,8 @@ namespace Segs { ModItem& mi = self->any_mods.back(); mi.index = pos; size_t j=0; - int thresh = 50; for (size_t m=0; m < std::min((size_t)4, (size_t)nm); ++m) { - if (mods[m].qual > thresh) { + if (mods[m].qual > parse_mods_threshold) { mi.mods[j] = (char)mods[m].modified_base; mi.quals[j] = (uint8_t)mods[m].qual; mi.strands[j] = (bool)mods[m].strand; @@ -818,22 +818,20 @@ namespace Segs { } void align_clear(Align *self) { -// self->block_starts.clear(); -// self->block_ends.clear(); self->blocks.clear(); self->any_ins.clear(); } - void init_parallel(std::vector &aligns, int n, BS::thread_pool &pool, const bool parse_mods) { + void init_parallel(std::vector &aligns, int n, BS::thread_pool &pool, const int parse_mods_threshold) { if (n == 1) { for (auto &aln : aligns) { - align_init(&aln, parse_mods); + align_init(&aln, parse_mods_threshold); } } else { pool.parallelize_loop(0, aligns.size(), - [&aligns, parse_mods](const int a, const int b) { + [&aligns, parse_mods_threshold](const int a, const int b) { for (int i = a; i < b; ++i) - align_init(&aligns[i], parse_mods); + align_init(&aligns[i], parse_mods_threshold); }) .wait(); } @@ -872,9 +870,7 @@ namespace Segs { void addToCovArray(std::vector &arr, const Align &align, const uint32_t begin, const uint32_t end, const uint32_t l_arr) noexcept { size_t n_blocks = align.blocks.size(); -// size_t n_blocks = align.block_starts.size(); for (size_t idx=0; idx < n_blocks; ++idx) { -// uint32_t block_s = align.block_starts[idx]; uint32_t block_s = align.blocks[idx].start; if (block_s >= end) { break; } uint32_t block_e = align.blocks[idx].end; diff --git a/src/segments.h b/src/segments.h index 9efdb35..e4243d7 100644 --- a/src/segments.h +++ b/src/segments.h @@ -119,11 +119,11 @@ namespace Segs { void clear(); }; - void align_init(Align *self, const bool parse_mods); + void align_init(Align *self, const int parse_mods_threshold); void align_clear(Align *self); - void init_parallel(std::vector &aligns, int n, BS::thread_pool &pool, const bool parse_mods); + void init_parallel(std::vector &aligns, int n, BS::thread_pool &pool, const int parse_mods_threshold); void resetCovStartEnd(ReadCollection &cl); diff --git a/src/term_out.cpp b/src/term_out.cpp index 703893a..776e822 100644 --- a/src/term_out.cpp +++ b/src/term_out.cpp @@ -581,6 +581,7 @@ namespace Term { } out << termcolor::bold << "flag " << termcolor::reset << r->delegate->core.flag << std::endl; out << termcolor::bold << "mapq " << termcolor::reset << (int)r->delegate->core.qual << std::endl; + out << termcolor::bold << "len " << termcolor::reset << (int)r->delegate->core.l_qseq << std::endl; out << termcolor::bold << "cigar " << termcolor::reset; printCigar(r, out); out << std::endl; out << termcolor::bold << "seq " << termcolor::reset; printSeq(r, refSeq, refStart, refEnd, 500, out, pos); out << std::endl << std::endl; diff --git a/src/themes.cpp b/src/themes.cpp index 6d65955..3c4a3fa 100644 --- a/src/themes.cpp +++ b/src/themes.cpp @@ -356,6 +356,7 @@ namespace Themes { start_index = 0; font_str = "Menlo"; font_size = 14; + mods_qual_threshold = 50; soft_clip_threshold = 20000; small_indel_threshold = 100000; @@ -581,6 +582,9 @@ namespace Themes { if (sub.has("font_size")) { font_size = std::stoi(sub["font_size"]); } + if (sub.has("mods_qual_threshold")) { + mods_qual_threshold = std::stoi(sub["mods_qual_threshold"]); + } } if (sesh.has("view_thresholds")) { mINI::INIMap& vt = sesh["view_thresholds"]; @@ -685,7 +689,6 @@ namespace Themes { std::vector>& variant_paths_info, std::vector& commands, std::string output_session, int mode, int window_x_pos, int window_y_pos, float monitorScale) { - std::cout << output_session << " , " << session_file << " saving session \n"; if (output_session.empty()) { if (session_file.empty()) { // fill new session @@ -774,6 +777,7 @@ namespace Themes { sub["tabix_track_height"] = std::to_string(tab_track_height); sub["font"] = font_str; sub["font_size"] = std::to_string(font_size); + sub["mods_qual_threshold"] = std::to_string(mods_qual_threshold); mINI::INIMap& vt = seshIni["view_thresholds"]; vt["soft_clip"] = std::to_string(soft_clip_threshold); diff --git a/src/themes.h b/src/themes.h index 1225711..1209832 100644 --- a/src/themes.h +++ b/src/themes.h @@ -147,7 +147,7 @@ namespace Themes { MenuTable menu_table; bool editing_underway; int canvas_width, canvas_height; - int indel_length, ylim, split_view_size, threads, pad, link_op, max_coverage, max_tlen; + 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; float scroll_speed, tab_track_height; int scroll_right; diff --git a/test/chr20:13829780-13866370_mod_call_sample.bam.bai b/test/chr20:13829780-13866370_mod_call_sample.bam.bai deleted file mode 100644 index 1013948d8b613efaada79331ea9f80a030c76a48..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6864 zcmeIyF$#o03ZqLYs7-(M_j8tT2A;X4tC{~y&5hZEJ@^+~-~b0WzyS_$fCC)h00%h00S<70103K0 Q2ROh14!m<<-7}x>4~Yq`kpKVy diff --git a/test/chr20:13829780-13866370_mod_call_sample.bam b/test/chr20_13822936-13833912.mod.bam similarity index 95% rename from test/chr20:13829780-13866370_mod_call_sample.bam rename to test/chr20_13822936-13833912.mod.bam index 0030e84d607e9ae2e98432cc70848d67bfe11e4e..18e48980923e50b6e1e2d6ccd635393bf6d02f21 100644 GIT binary patch delta 318 zcmV-E0m1&($_CTQ27e!m2m}BC000301^_}s0st-ng^=w|gD@0^nI<#cRrCTzp`{@3 z3uSSdOvI4r&!&7hNPsTDoL;H7V?pBAHf_@8&B^KeKIdFs+)kz^isD@_z(e3-WP#|; zO@wIDqUK#T^P+nY1#XfzLR5F QI0F?RgD(NMF9883q{YXR<^TWy delta 321 zcmV-H0lxmz$_CcT27e!m2m}BC000301^_}s0st`qg^}$}gD@0^nI@XAA!7L>$giS#PWAgHfa-+H0Pr>q2RZ z1Kz}KL&M(EeHjmL_3hXS5MqFPv+ znbeqJI+uG%<}q{tqm9Cl9>zQhbHE_pt1ND#2MKgEK|7HUj77AcGLaVU3bKTLf6$4PyvH%laf^|Za^e<9k%leh2ywCJ)nuuu2owuwJ zLmibf9<}MO{(jC9&cHLbbT#vzskt$Gum}Hw3mo782ROh14sd`29N+*4IKTl8aDW3G R-~b0Wz=3xTtb6A3{Q-g4q?iBz literal 0 HcmV?d00001 From 8712e8dfb7f178ea19d89967223e8a1fef8ee33b Mon Sep 17 00:00:00 2001 From: kcleal Date: Sun, 7 Jul 2024 15:11:18 +0100 Subject: [PATCH 47/51] Mods+ideogram work with session file. Mods qual threshold option. Read length to terminal. No crash when chrom not in ref at startup. Mods displayed in terminal. Printing read improvements --- src/menu.cpp | 2 +- src/plot_controls.cpp | 23 ++-- src/plot_manager.cpp | 4 +- src/term_out.cpp | 239 +++++++++++++++++++++++++++++------------- src/term_out.h | 2 +- src/themes.cpp | 5 +- src/themes.h | 2 +- 7 files changed, 190 insertions(+), 87 deletions(-) diff --git a/src/menu.cpp b/src/menu.cpp index 8204fbf..05a5a32 100644 --- a/src/menu.cpp +++ b/src/menu.cpp @@ -307,7 +307,7 @@ namespace Menu { else if (opts.menu_level == "edge_highlights") { tip = "The distance in base-pairs when edge-highlights become visible"; } else if (opts.menu_level == "low_memory") { tip = "The distance in base-pairs when using low-memory mode (reads are not buffered in this mode)"; } else if (opts.menu_level == "mods") { tip = "Display modified bases"; } - else if (opts.menu_level == "mods_qual_threshold") { tip = "Threshold for displaying modified bases [0-255]"; } + else if (opts.menu_level == "mods_qual_threshold") { tip = "Threshold (>) for displaying modified bases [0-255]"; } else if (opts.menu_level == "scroll_right") { tip = "Keyboard key to use for scrolling right"; } else if (opts.menu_level == "scroll_left") { tip = "Keyboard key to use for scrolling left"; } else if (opts.menu_level == "scroll_down") { tip = "Keyboard key to use for scrolling down"; } diff --git a/src/plot_controls.cpp b/src/plot_controls.cpp index e966312..10e6705 100644 --- a/src/plot_controls.cpp +++ b/src/plot_controls.cpp @@ -1304,14 +1304,19 @@ namespace Manager { return; } // highlight read below - int level = 0; + int level = -1; int slop = 0; if (!opts.tlen_yscale) { - level = (int)((yW - (float) cl.yOffset) / yScaling); + if (yW < cl.yOffset) { + out << std::endl; + return; + } + level = ((yW - (float) cl.yOffset) / yScaling); if (level < 0) { // print coverage info (mouse Pos functions already prints out cov info to console) out << std::endl; return; } + level = (int)level; if (cl.vScroll < 0) { level += cl.vScroll + 1; } @@ -1321,10 +1326,12 @@ namespace Manager { slop = (int)(max_bound * 0.025); slop = (slop <= 0) ? 25 : slop; } + std::cout << " level " << level << std::endl; std::vector::iterator bnd; bnd = std::lower_bound(cl.readQueue.begin(), cl.readQueue.end(), pos, [&](const Segs::Align &lhs, const int pos) { return (int)lhs.pos <= pos; }); - while (bnd != cl.readQueue.begin()) { + + while (true) { if (!opts.tlen_yscale) { if (bnd->y == level && (int)bnd->pos <= pos && pos < (int)bnd->reference_end) { if (bnd->edge_type == 4) { @@ -1336,10 +1343,10 @@ namespace Manager { bnd->edge_type = 1; // "NORMAL" } target_qname = ""; - } else { + } else if (bnd->delegate != nullptr) { bnd->edge_type = 4; target_qname = bam_get_qname(bnd->delegate); - Term::printRead(bnd, headers[cl.bamIdx], selectedAlign, cl.region->refSeq, cl.region->start, cl.region->end, opts.low_memory, out, pos); + Term::printRead(bnd, headers[cl.bamIdx], selectedAlign, cl.region->refSeq, cl.region->start, cl.region->end, opts.low_memory, out, pos, opts.indel_length, opts.parse_mods); } redraw = true; processed = true; @@ -1348,6 +1355,7 @@ namespace Manager { break; } } else { + if ((bnd->y >= level - slop && bnd->y < level) && (int)bnd->pos <= pos && pos < (int)bnd->reference_end) { if (bnd->edge_type == 4) { if (bnd->has_SA || bnd->delegate->core.flag & 2048) { @@ -1361,7 +1369,7 @@ namespace Manager { } else { bnd->edge_type = 4; target_qname = bam_get_qname(bnd->delegate); - Term::printRead(bnd, headers[cl.bamIdx], selectedAlign, cl.region->refSeq, cl.region->start, cl.region->end, opts.low_memory, out, pos); + Term::printRead(bnd, headers[cl.bamIdx], selectedAlign, cl.region->refSeq, cl.region->start, cl.region->end, opts.low_memory, out, pos, opts.indel_length, opts.parse_mods); } redraw = true; processed = true; @@ -1369,6 +1377,9 @@ namespace Manager { cl.skipDrawingCoverage = true; } } + if (bnd == cl.readQueue.begin()) { + break; + } --bnd; } xDrag = DRAG_UNSET; diff --git a/src/plot_manager.cpp b/src/plot_manager.cpp index 385c242..30db25c 100644 --- a/src/plot_manager.cpp +++ b/src/plot_manager.cpp @@ -536,8 +536,10 @@ namespace Manager { } 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, - "", mode, xpos, ypos, monitorScale); + "", mode, xpos, ypos, monitorScale, windX, windY); } int GwPlot::startUI(GrDirectContext* sContext, SkSurface *sSurface, int delay) { diff --git a/src/term_out.cpp b/src/term_out.cpp index 776e822..ccd283b 100644 --- a/src/term_out.cpp +++ b/src/term_out.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #if !defined(__EMSCRIPTEN__) #include @@ -325,12 +326,15 @@ namespace Term { } } - void printSeq(std::vector::iterator r, const char *refSeq, int refStart, int refEnd, int max, std::ostream& out, int pos, int indel_length=30) { + void printSeq(std::vector::iterator r, const char *refSeq, int refStart, int refEnd, int max, std::ostream& out, int pos, int indel_length, bool show_mod, const char* target_mod) { auto l_seq = (int)r->delegate->core.l_qseq; if (l_seq == 0) { out << "*"; return; } + auto mod_it = r->any_mods.begin(); + auto mod_end = r->any_mods.end(); + uint32_t cigar_l, op, k; int l; uint32_t *cigar_p; @@ -346,11 +350,18 @@ namespace Term { l = (int)(cigar_p[k] >> BAM_CIGAR_SHIFT); int block_end = p + l; + bool overlaps = (pos >= p && pos <= block_end); + + if (show_mod) { + while (mod_it != mod_end && mod_it->index < i) { + ++mod_it; + } + } if (op == BAM_CHARD_CLIP) { continue; } else if (op == BAM_CDEL) { - if (started || std::abs(pos - p) < max || std::abs(pos - block_end) < max) { + if (started || std::abs(pos - p) < max / 2 || std::abs(pos - block_end) < max / 2 || overlaps) { started = true; } else { p += l; @@ -383,13 +394,14 @@ namespace Term { p += l; } else if (op == BAM_CMATCH) { - if (std::abs(pos - p) < max || std::abs(pos - block_end)) { + if (started || std::abs(pos - p) < max / 2 || std::abs(pos - block_end) < max / 2 || overlaps) { started = true; } else { p += l; i += l; continue; } + printed += l; for (int n = 0; n < l; ++n) { uint8_t base = bam_seqi(ptr_seq, i); @@ -397,9 +409,13 @@ namespace Term { if (p >= refStart && p < refEnd && refSeq != nullptr && std::toupper(refSeq[p - refStart]) != basemap[base]) { mm = true; } - if (mm) { - out << termcolor::underline; - switch (basemap[base]) { + if (p == pos) { + out << termcolor::bold; + } + if (!show_mod) { + if (mm) { + out << termcolor::underline; + switch (basemap[base]) { case 65 : out << termcolor::green << "A" << termcolor::reset; break; @@ -416,27 +432,40 @@ namespace Term { out << termcolor::red << "T" << termcolor::reset; break; } + } else { + switch (basemap[base]) { + case 65 : + out << "A"; + break; + case 67 : + out << "C"; + break; + case 71 : + out << "G"; + break; + case 78 : + out << "N"; + break; + case 84 : + out << "T"; + break; + } + } } else { - switch (basemap[base]) { - case 65 : - out << "A"; - break; - case 67 : - out << "C"; - break; - case 71 : - out << "G"; - break; - case 78 : - out << "N"; - break; - case 84 : - out << "T"; - break; + if (mod_it != mod_end && i == mod_it->index) { + out << "M"; + ++mod_it; + } else { + out << "."; } } + i += 1; + if (p == pos) { + out << termcolor::reset ; + } p += 1; + } if (printed > max * 2) { out << "..."; @@ -444,7 +473,7 @@ namespace Term { } } else if (op == BAM_CEQUAL) { - if (std::abs(pos - p) < max || std::abs(pos - block_end)) { + if (started || std::abs(pos - p) < max / 2 || std::abs(pos - block_end) < max / 2 || overlaps) { started = true; } else { p += l; @@ -454,33 +483,47 @@ namespace Term { printed += l; for (int n = 0; n < (int)l; ++n) { uint8_t base = bam_seqi(ptr_seq, i); - switch (basemap[base]) { - case 65 : - out << "A"; - break; - case 67 : - out << "C"; - break; - case 71 : - out << "G"; - break; - case 78 : - out << "N"; - break; - case 84 : - out << "T"; - break; + if (p == pos) { + out << termcolor::bold; + } + if (!show_mod) { + switch (basemap[base]) { + case 65 : + out << "A"; + break; + case 67 : + out << "C"; + break; + case 71 : + out << "G"; + break; + case 78 : + out << "N"; + break; + case 84 : + out << "T"; + break; + } + } else { + if (mod_it != mod_end && i == mod_it->index) { + out << "M"; + ++mod_it; + } else { + out << "."; + } } i += 1; + p += 1; + out << termcolor::reset; } - p += l; - if (printed > max * 2) { +// p += l; + if (printed > max / 2) { out << "..."; break; } - } else if (started && (op == BAM_CDIFF || op == BAM_CINS)) { - if (std::abs(pos - p) < max || std::abs(pos - block_end)) { + } else if (op == BAM_CDIFF || op == BAM_CINS) { + if (started || std::abs(pos - p) < max / 2 || std::abs(pos - block_end) < max / 2 || overlaps) { started = true; } else { if (op == BAM_CDIFF) { @@ -493,38 +536,49 @@ namespace Term { if (op == BAM_CINS && l > indel_length) { out << termcolor::magenta << "[" << std::to_string(l) << "]" << termcolor::reset; } - for (int n = 0; n < (int)l; ++n) { - uint8_t base = bam_seqi(ptr_seq, i); - switch (basemap[base]) { - case 65 : - out << termcolor::green << "A" << termcolor::reset; - break; - case 67 : - out << termcolor::blue << "C" << termcolor::reset; - break; - case 71 : - out << termcolor::yellow << "G" << termcolor::reset; - break; - case 78 : - out << termcolor::grey << "N" << termcolor::reset; - break; - case 84 : - out << termcolor::red << "T" << termcolor::reset; - break; + for (int n = 0; n < l; ++n) { + + if (!show_mod) { + uint8_t base = bam_seqi(ptr_seq, i); + switch (basemap[base]) { + case 65 : + out << termcolor::green << "A" << termcolor::reset; + break; + case 67 : + out << termcolor::blue << "C" << termcolor::reset; + break; + case 71 : + out << termcolor::yellow << "G" << termcolor::reset; + break; + case 78 : + out << termcolor::grey << "N" << termcolor::reset; + break; + case 84 : + out << termcolor::red << "T" << termcolor::reset; + break; + } + } else { + if (mod_it != mod_end && i == mod_it->index) { + out << "M"; + ++mod_it; + } else { + out << "."; + } } i += 1; } if (op == BAM_CDIFF) { p += l; } - if (op == BAM_CINS && printed > max * 2) { - out << "..."; - break; - } +// if (op == BAM_CINS && printed > max * 2) { +// out << "..."; +// break; +// } - } else { + } else { // soft-clips if (k == 0) { if (pos - p > max) { + i = l; continue; } else { started = true; @@ -542,13 +596,32 @@ namespace Term { } i += n; for (; n < stop; ++n) { // soft-clips - uint8_t base = bam_seqi(ptr_seq, i); - switch (basemap[base]) { - case 65 : out << termcolor::green << "A" << termcolor::reset; break; - case 67 : out << termcolor::blue << "C" << termcolor::reset; break; - case 71 : out << termcolor::yellow << "G" << termcolor::reset; break; - case 78 : out << termcolor::grey << "N" << termcolor::reset; break; - case 84 : out << termcolor::red << "T" << termcolor::reset; break; + if (!show_mod) { + uint8_t base = bam_seqi(ptr_seq, i); + switch (basemap[base]) { + case 65 : + out << termcolor::green << "A" << termcolor::reset; + break; + case 67 : + out << termcolor::blue << "C" << termcolor::reset; + break; + case 71 : + out << termcolor::yellow << "G" << termcolor::reset; + break; + case 78 : + out << termcolor::grey << "N" << termcolor::reset; + break; + case 84 : + out << termcolor::red << "T" << termcolor::reset; + break; + } + } else { + if (mod_it != mod_end && i == mod_it->index) { + out << "M"; + ++mod_it; + } else { + out << "."; + } } i += 1; } @@ -570,9 +643,12 @@ namespace Term { return; } - void printRead(std::vector::iterator r, const sam_hdr_t* hdr, std::string &sam, const char *refSeq, int refStart, int refEnd, bool low_mem, std::ostream& out, int pos) { + void printRead(std::vector::iterator r, const sam_hdr_t* hdr, std::string &sam, const char *refSeq, int refStart, int refEnd, bool low_mem, std::ostream& out, int pos, int indel_length, bool show_mod) { const char *rname = sam_hdr_tid2name(hdr, r->delegate->core.tid); const char *rnext = sam_hdr_tid2name(hdr, r->delegate->core.mtid); + + int term_width = std::max(Utils::get_terminal_width() * 2 - 9, 50); + out << std::endl << std::endl; out << termcolor::bold << "qname " << termcolor::reset << bam_get_qname(r->delegate) << std::endl; out << termcolor::bold << "span " << termcolor::reset << rname << ":" << r->pos << "-" << r->reference_end << std::endl; @@ -583,8 +659,21 @@ namespace Term { out << termcolor::bold << "mapq " << termcolor::reset << (int)r->delegate->core.qual << std::endl; out << termcolor::bold << "len " << termcolor::reset << (int)r->delegate->core.l_qseq << std::endl; out << termcolor::bold << "cigar " << termcolor::reset; printCigar(r, out); out << std::endl; - out << termcolor::bold << "seq " << termcolor::reset; printSeq(r, refSeq, refStart, refEnd, 500, out, pos); out << std::endl << std::endl; + out << termcolor::bold << "seq " << termcolor::reset; printSeq(r, refSeq, refStart, refEnd, term_width, out, pos, indel_length, false, ""); out << std::endl << std::endl; + if (show_mod) { + std::unordered_set mods; // make a list of mods in this read + for (const auto& m : r->any_mods) { + for (int n=0; n < m.n_mods; ++n) { + mods.insert(m.mods[n]); + } + } + for (const auto& mod_type : mods) { + + out << termcolor::bold << "mod " << mod_type << " " << termcolor::reset; printSeq(r, refSeq, refStart, refEnd, term_width, out, pos, indel_length, true, &mod_type); out << std::endl << std::endl; + } +// + } read2sam(r, hdr, sam, low_mem, out); } diff --git a/src/term_out.h b/src/term_out.h index e2dc5f2..e276d38 100644 --- a/src/term_out.h +++ b/src/term_out.h @@ -29,7 +29,7 @@ namespace Term { void editInputText(std::string &inputText, const char *letter, int &charIndex); void printRead(std::vector::iterator r, const sam_hdr_t* hdr, std::string &sam, const char *refSeq, - int refStart, int refEnd, bool low_mem, std::ostream& out, int pos); + int refStart, int refEnd, bool low_mem, std::ostream& out, int pos, int indel_length, bool show_mod); void printSelectedSam(std::string &sam, std::ostream& out); diff --git a/src/themes.cpp b/src/themes.cpp index 3c4a3fa..cd0f9f5 100644 --- a/src/themes.cpp +++ b/src/themes.cpp @@ -688,7 +688,8 @@ namespace Themes { std::vector& track_paths, std::vector& regions, std::vector>& variant_paths_info, std::vector& commands, std::string output_session, - int mode, int window_x_pos, int window_y_pos, float monitorScale) { + int mode, int window_x_pos, int window_y_pos, float monitorScale, + int screen_width, int screen_height) { if (output_session.empty()) { if (session_file.empty()) { // fill new session @@ -764,7 +765,7 @@ namespace Themes { } mINI::INIMap& sub = seshIni["general"]; sub["theme"] = theme_str; - sub["dimensions"] = std::to_string(dimensions.x) + "x" + std::to_string(dimensions.y); + sub["dimensions"] = std::to_string(screen_width) + "x" + std::to_string(screen_height); sub["ylim"] = std::to_string(ylim); sub["coverage"] = (max_coverage) ? "true" : "false"; diff --git a/src/themes.h b/src/themes.h index 1209832..bf5f83c 100644 --- a/src/themes.h +++ b/src/themes.h @@ -179,7 +179,7 @@ namespace Themes { std::vector& track_paths, std::vector& regions, std::vector>& variant_paths_info, std::vector& commands, std::string output_session, - int mode, int window_x_pos, int window_y_pos, float monitorScale); + int mode, int window_x_pos, int window_y_pos, float monitorScale, int screen_width, int screen_height); }; From 1e571f6d7b61cd449465985a891634f30a75ede0 Mon Sep 17 00:00:00 2001 From: kcleal Date: Sun, 7 Jul 2024 19:17:41 +0100 Subject: [PATCH 48/51] new 5mc color. added option for commenting in out stream using ' or quote. Faster mod drawing. Color options for mods. Mods output improvements --- src/drawing.cpp | 47 +++++++++++++++++++------------------------ src/plot_commands.cpp | 45 ++++++++++------------------------------- src/plot_manager.cpp | 6 +++--- src/term_out.cpp | 42 ++++++++++++++++++++++++++++---------- src/themes.cpp | 14 +++++++++++++ src/themes.h | 4 ++-- 6 files changed, 82 insertions(+), 76 deletions(-) diff --git a/src/drawing.cpp b/src/drawing.cpp index e917fa0..6514e72 100644 --- a/src/drawing.cpp +++ b/src/drawing.cpp @@ -768,7 +768,7 @@ namespace Drawing { void drawMods(SkCanvas *canvas, SkRect &rect, const Themes::BaseTheme &theme, const Utils::Region *region, const Segs::Align &align, float width, float xScaling, float xOffset, float mmPosOffset, float yScaledOffset, - float pH, int l_qseq, float monitorScale) { + float pH, int l_qseq, float monitorScale, SkPaint& fc5mc, SkPaint& fc5hmc, SkPaint& fcOther) { if (align.any_mods.empty()) { return; } @@ -777,19 +777,8 @@ namespace Drawing { auto mod_it = align.any_mods.begin(); auto mod_end = align.any_mods.end(); - SkPaint painth; - painth.setARGB(127, 52, 255, 96); - painth.setStrokeWidth(std::fmin(pH / 3, 4 * monitorScale)); - painth.setAntiAlias(true); - painth.setStrokeCap(SkPaint::kRound_Cap); - - SkPaint paintm; - paintm.setARGB(127, 252, 186, 3); - paintm.setStrokeWidth(std::fmin(pH / 3, 4 * monitorScale)); - paintm.setAntiAlias(true); - paintm.setStrokeCap(SkPaint::kRound_Cap); - float top = yScaledOffset + (pH / 3); + float middle = yScaledOffset + pH - (pH / 2); float bottom = yScaledOffset + pH - (pH / 3); for (const auto& blk : align.blocks) { @@ -805,27 +794,26 @@ namespace Drawing { } while (mod_it != mod_end && mod_it->index < idx_end) { float x = (((blk.start + mod_it->index - idx_start) - region->start) * xScaling) + precalculated_xOffset_mmPosOffset; - for (size_t j=0; j < mod_it->n_mods; ++j) { + int n_mods = mod_it->n_mods; + for (size_t j=0; j < (size_t)n_mods; ++j) { switch (mod_it->mods[j]) { case 'm': // 5mC - paintm.setAlpha(mod_it->quals[j]); - canvas->drawPoint(x, top, paintm); + fc5mc.setAlpha(mod_it->quals[j]); + canvas->drawPoint(x, (n_mods == 1) ? middle : top, fc5mc); break; case 'h': // 5hmC - painth.setAlpha(mod_it->quals[j]); - canvas->drawPoint(x, bottom, painth); + fc5mc.setAlpha(mod_it->quals[j]); + canvas->drawPoint(x, (n_mods == 1) ? middle : bottom, fc5hmc); break; default: - // todo other - + fcOther.setAlpha(mod_it->quals[j]); + canvas->drawPoint(x, middle, fcOther); break; } } ++mod_it; } - } - } void drawCollection(const Themes::IniOptions &opts, Segs::ReadCollection &cl, @@ -866,6 +854,16 @@ namespace Drawing { cl.skipDrawingReads = true; + SkPaint fc5mc, fc5hmc, fcOther; + if (opts.parse_mods) { + fc5mc = theme.fc5mc; + fc5hmc = theme.fc5hmc; + fcOther = theme.fcA; // todo option for this + fc5mc.setStrokeWidth(std::fmin(pH / 3, 4 * monitorScale)); + fc5hmc.setStrokeWidth(std::fmin(pH / 3, 4 * monitorScale)); + fcOther.setStrokeWidth(std::fmin(pH / 3, 4 * monitorScale)); + } + for (const auto &a: cl.readQueue) { int Y = a.y; if (Y < 0) { @@ -1035,7 +1033,7 @@ namespace Drawing { } if (opts.parse_mods && regionLen <= opts.mod_threshold) { drawMods(canvas, rect, theme, cl.region, a, (float) width, xScaling, xOffset, mmPosOffset, - yScaledOffset, pH, l_qseq, monitorScale); + yScaledOffset, pH, l_qseq, monitorScale, fc5mc, fc5hmc, fcOther); } // add insertions @@ -1107,11 +1105,8 @@ namespace Drawing { canvas->drawTextBlob(t.text.get(), t.x + (monitorScale * 0.5), t.y, theme.tcDel); } for (const auto &t : text_ins) { - - const SkRect& bounds = t.text->bounds(); rect.setXYWH(t.x - monitorScale, t.box_y, t.box_w, pH); canvas->drawRect(rect, theme.fcIns); - canvas->drawTextBlob(t.text.get(), t.x + (monitorScale * 0.5), t.y, theme.tcIns); } diff --git a/src/plot_commands.cpp b/src/plot_commands.cpp index e54ccf6..97a6fa2 100644 --- a/src/plot_commands.cpp +++ b/src/plot_commands.cpp @@ -220,11 +220,7 @@ namespace Commands { Err insertions(Plot* p) { p->opts.small_indel_threshold = (p->opts.small_indel_threshold == 0) ? std::stoi(p->opts.myIni["view_thresholds"]["small_indel"]) : 0; - if (p->mode == Manager::Show::SINGLE) { - p->processed = true; - } else { - p->processed = false; - } + p->processed = false; p->redraw = true; p->imageCache.clear(); return Err::NONE; @@ -232,15 +228,7 @@ namespace Commands { Err mismatches(Plot* p) { p->opts.snp_threshold = (p->opts.snp_threshold == 0) ? std::stoi(p->opts.myIni["view_thresholds"]["snp"]) : 0; - if (p->mode == Manager::Show::SINGLE) { - p->processed = true; - for (auto &cl : p->collections) { - cl.skipDrawingReads = false; - cl.skipDrawingCoverage = false; - } - } else { - p->processed = false; - } + p->processed = false; p->redraw = true; p->imageCache.clear(); return Err::NONE; @@ -248,16 +236,7 @@ namespace Commands { Err mods(Plot* p) { p->opts.parse_mods = !(p->opts.parse_mods); -// p->opts.mod_threshold = (p->opts.mod_threshold == 0) ? std::stoi(p->opts.myIni["view_thresholds"]["mod"]) : 0; - if (p->mode == Manager::Show::SINGLE) { - p->processed = false; - for (auto &cl : p->collections) { - cl.skipDrawingReads = false; - cl.skipDrawingCoverage = false; - } - } else { - p->processed = false; - } + p->processed = false; p->redraw = true; p->imageCache.clear(); return Err::NONE; @@ -265,15 +244,7 @@ namespace Commands { Err edges(Plot* p) { p->opts.edge_highlights = (p->opts.edge_highlights == 0) ? std::stoi(p->opts.myIni["view_thresholds"]["edge_highlights"]) : 0; - if (p->mode == Manager::Show::SINGLE) { - p->processed = true; - for (auto & cl: p->collections) { - cl.skipDrawingReads = false; - cl.skipDrawingCoverage = true; - } - } else { - p->processed = false; - } + p->processed = false; p->redraw = true; p->imageCache.clear(); return Err::NONE; @@ -1169,6 +1140,8 @@ namespace Commands { else if (c == "tcBackground") { e = Themes::GwPaint::tcBackground; } else if (c == "fcMarkers") { e = Themes::GwPaint::fcMarkers; } else if (c == "fcRoi") { e = Themes::GwPaint::fcRoi; } + else if (c == "fc5mc") { e = Themes::GwPaint::fc5mc; } + else if (c == "fc5hmc") { e = Themes::GwPaint::fc5hmc; } else { return Err::OPTION_NOT_UNDERSTOOD; } @@ -1288,7 +1261,11 @@ 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, "\"")) { + out << command << std::endl; + command = ""; + return; + } std::vector parts = Utils::split(command, ' '); static std::unordered_map&, std::ostream& out)>> functionMap = { diff --git a/src/plot_manager.cpp b/src/plot_manager.cpp index 30db25c..a8d52e4 100644 --- a/src/plot_manager.cpp +++ b/src/plot_manager.cpp @@ -1023,7 +1023,7 @@ namespace Manager { } Menu::drawMenu(canvas, opts, fonts, monitorScale, fb_width, fb_height, inputText, charIndex); - // box for change in region selection using keyboard + // draw box when a change in region selection happens via keyboard } else if (regionSelectionTriggered && regions.size() > 1) { SkRect rect{}; float step = (float)fb_width / (float)regions.size(); @@ -1141,9 +1141,9 @@ namespace Manager { sk_sp blob = SkTextBlob::MakeFromString(Menu::commandToolTip[commandToolTipIndex], fonts.overlay); SkPaint grey; grey.setColor(SK_ColorGRAY); - canvas->drawTextBlob(blob, x + 14 + fonts.overlayWidth + fonts.overlayWidth, yy + (fonts.overlayHeight * 1.3), grey); + canvas->drawTextBlob(blob, x + 14 + fonts.overlayWidth + fonts.overlayWidth, yy + (fonts.overlayHeight * 1.3) + pad + pad, grey); } - yy += pad; + //yy += pad; SkPaint tip_paint = opts.theme.lcBright; tip_paint.setAntiAlias(true); diff --git a/src/term_out.cpp b/src/term_out.cpp index ccd283b..f8b833c 100644 --- a/src/term_out.cpp +++ b/src/term_out.cpp @@ -367,7 +367,7 @@ namespace Term { p += l; continue; } - started = true; + printed += l; std::string str = std::to_string(l); if (l > max) { for (int n = 0; n < 100; ++n) { @@ -392,6 +392,10 @@ namespace Term { } } p += l; + if (printed > max) { + out << "..."; + break; + } } else if (op == BAM_CMATCH) { if (started || std::abs(pos - p) < max / 2 || std::abs(pos - block_end) < max / 2 || overlaps) { @@ -453,7 +457,12 @@ namespace Term { } } else { if (mod_it != mod_end && i == mod_it->index) { - out << "M"; + if (*target_mod == 'm') { + out << termcolor::on_yellow << termcolor::grey; + } else if (*target_mod == 'h') { + out << termcolor::on_green << termcolor::grey; + } + out << "M" << termcolor::reset; ++mod_it; } else { out << "."; @@ -467,7 +476,7 @@ namespace Term { p += 1; } - if (printed > max * 2) { + if (printed > max) { out << "..."; break; } @@ -506,7 +515,12 @@ namespace Term { } } else { if (mod_it != mod_end && i == mod_it->index) { - out << "M"; + if (*target_mod == 'm') { + out << termcolor::on_yellow << termcolor::grey; + } else if (*target_mod == 'h') { + out << termcolor::on_green << termcolor::grey; + } + out << "=" << termcolor::reset; ++mod_it; } else { out << "."; @@ -559,7 +573,13 @@ namespace Term { } } else { if (mod_it != mod_end && i == mod_it->index) { - out << "M"; + char o = (op == BAM_CINS) ? 'I' : 'X'; + if (*target_mod == 'm') { + out << termcolor::on_yellow << termcolor::grey; + } else if (*target_mod == 'h') { + out << termcolor::on_green << termcolor::grey; + } + out << o << termcolor::reset; ++mod_it; } else { out << "."; @@ -570,10 +590,10 @@ namespace Term { if (op == BAM_CDIFF) { p += l; } -// if (op == BAM_CINS && printed > max * 2) { -// out << "..."; -// break; -// } + if (printed > max) { + out << "..."; + break; + } } else { // soft-clips if (k == 0) { @@ -617,7 +637,7 @@ namespace Term { } } else { if (mod_it != mod_end && i == mod_it->index) { - out << "M"; + out << "S"; ++mod_it; } else { out << "."; @@ -670,7 +690,7 @@ namespace Term { } for (const auto& mod_type : mods) { - out << termcolor::bold << "mod " << mod_type << " " << termcolor::reset; printSeq(r, refSeq, refStart, refEnd, term_width, out, pos, indel_length, true, &mod_type); out << std::endl << std::endl; + out << termcolor::bold << "mod '" << mod_type << "' " << termcolor::reset; printSeq(r, refSeq, refStart, refEnd, term_width, out, pos, indel_length, true, &mod_type); out << std::endl << std::endl; } // } diff --git a/src/themes.cpp b/src/themes.cpp index cd0f9f5..425d67c 100644 --- a/src/themes.cpp +++ b/src/themes.cpp @@ -148,6 +148,11 @@ namespace Themes { fcMarkers.setStrokeMiter(0.1); fcMarkers.setStrokeWidth(0.5); + fc5mc.setAntiAlias(true); + fc5mc.setStrokeCap(SkPaint::kRound_Cap); + fc5hmc.setAntiAlias(true); + fc5hmc.setStrokeCap(SkPaint::kRound_Cap); + for (size_t i=0; i < mate_fc.size(); ++i) { SkPaint p = mate_fc[i]; mate_fc[i].setAlpha(alpha); @@ -247,6 +252,9 @@ namespace Themes { fcC.setARGB(255, 66, 127, 255); fcG.setARGB(255, 235, 150, 23); fcN.setARGB(255, 128, 128, 128); +// fc5mc.setARGB(127, 255, 94, 0); + fc5mc.setARGB(127, 30, 176, 230); + fc5hmc.setARGB(127, 52, 255, 96); lcJoins.setARGB(255, 80, 80, 80); lcLightJoins.setARGB(255, 140, 140, 140); lcLabel.setARGB(255, 80, 80, 80); @@ -282,6 +290,9 @@ namespace Themes { fcC.setARGB(255, 77, 125, 245); fcG.setARGB(255, 226, 132, 19); fcN.setARGB(255, 128, 128, 128); +// fc5mc.setARGB(127, 252, 186, 3); + fc5mc.setARGB(127, 30, 176, 230); + fc5hmc.setARGB(127, 52, 255, 96); lcJoins.setARGB(255, 142, 142, 142); lcLightJoins.setARGB(255, 82, 82, 82); lcLabel.setARGB(255, 182, 182, 182); @@ -318,6 +329,9 @@ namespace Themes { fcC.setARGB(255, 77, 155, 245); fcG.setARGB(255, 226, 132, 19); fcN.setARGB(255, 128, 128, 128); +// fc5mc.setARGB(127, 252, 186, 3); + fc5mc.setARGB(127, 30, 176, 230); + fc5hmc.setARGB(127, 52, 255, 96); lcJoins.setARGB(255, 142, 142, 142); lcLightJoins.setARGB(255, 82, 82, 82); lcLabel.setARGB(255, 182, 182, 182); diff --git a/src/themes.h b/src/themes.h index bf5f83c..ef663cf 100644 --- a/src/themes.h +++ b/src/themes.h @@ -72,7 +72,7 @@ namespace Themes { fcA, fcT, fcC, fcG, fcN, fcCoverage, fcTrack, fcNormal0, fcDel0, fcDup0, fcInvF0, fcInvR0, fcTra0, fcSoftClip0, fcBigWig, fcRoi, mate_fc, mate_fc0, ecMateUnmapped, ecSplit, ecSelected, lcJoins, lcCoverage, lcLightJoins, lcLabel, lcBright, tcDel, tcIns, tcLabels, tcBackground, - fcMarkers + fcMarkers, fc5mc, fc5hmc }; class EXPORT BaseTheme { @@ -84,7 +84,7 @@ namespace Themes { // face colours SkPaint bgPaint, bgMenu, fcNormal, fcDel, fcDup, fcInvF, fcInvR, fcTra, fcIns, fcSoftClip, \ fcA, fcT, fcC, fcG, fcN, fcCoverage, fcTrack, fcRoi; - SkPaint fcNormal0, fcDel0, fcDup0, fcInvF0, fcInvR0, fcTra0, fcSoftClip0, fcBigWig; + SkPaint fcNormal0, fcDel0, fcDup0, fcInvF0, fcInvR0, fcTra0, fcSoftClip0, fcBigWig, fc5mc, fc5hmc; std::vector mate_fc; std::vector mate_fc0; From f69f05b594daccc5918b5b1f598e9faafa369368 Mon Sep 17 00:00:00 2001 From: kcleal Date: Mon, 8 Jul 2024 11:37:48 +0100 Subject: [PATCH 49/51] Better ins drawing. bug fixes. session saving and loading. png saving. --- src/drawing.cpp | 12 +++-- src/main.cpp | 11 ++++- src/plot_commands.cpp | 39 ++++++++++++++-- src/plot_controls.cpp | 29 ++++++++++-- src/plot_manager.cpp | 100 ++++++++++++++++-------------------------- src/plot_manager.h | 7 ++- src/segments.cpp | 22 +++++++--- src/segments.h | 2 +- src/term_out.cpp | 10 ++++- src/themes.cpp | 16 +++---- src/themes.h | 2 +- 11 files changed, 153 insertions(+), 97 deletions(-) diff --git a/src/drawing.cpp b/src/drawing.cpp index 6514e72..ea7c300 100644 --- a/src/drawing.cpp +++ b/src/drawing.cpp @@ -1059,7 +1059,7 @@ namespace Drawing { drawIns(canvas, Y, p, yScaling, xOffset, yOffset, theme.fcIns, rect, pH, ins_block_h, ins_block_w); } } else if (regionLen < opts.small_indel_threshold) { // line only - drawIns(canvas, Y, p, yScaling, xOffset, yOffset, theme.fcIns,rect, pH, ins_block_h, ins_block_w); + drawIns(canvas, Y, p, yScaling, xOffset, yOffset, theme.fcIns, rect, pH, ins_block_h, ins_block_w); } } } @@ -1100,14 +1100,18 @@ namespace Drawing { } } - // draw text last + // draw text deletions + insertions for (const auto &t : text_del) { canvas->drawTextBlob(t.text.get(), t.x + (monitorScale * 0.5), t.y, theme.tcDel); } for (const auto &t : text_ins) { - rect.setXYWH(t.x - monitorScale, t.box_y, t.box_w, pH); + rect.setXYWH(t.x - monitorScale, t.box_y, t.box_w, pH); // middle canvas->drawRect(rect, theme.fcIns); - canvas->drawTextBlob(t.text.get(), t.x + (monitorScale * 0.5), t.y, theme.tcIns); + rect.setXYWH(t.x - monitorScale - ins_block_h, t.box_y, t.box_w + ins_block_h + ins_block_h, ins_block_h); // top + canvas->drawRect(rect, theme.fcIns); + rect.setXYWH(t.x - monitorScale - ins_block_h, t.box_y + pH - ins_block_h, t.box_w + ins_block_h + ins_block_h, ins_block_h); // bottom + canvas->drawRect(rect, theme.fcIns); + canvas->drawTextBlob(t.text.get(), t.x, t.y + pH, theme.tcIns); } // draw connecting lines between linked alignments diff --git a/src/main.cpp b/src/main.cpp index 7dd63fb..0b33774 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -153,6 +153,9 @@ int main(int argc, char *argv[]) { program.add_argument("--out-labels") .default_value(std::string{""}).append() .help("Output labelling results to tab-separated FILE (use with -v or -i)"); + program.add_argument("--session") + .default_value(std::string{""}).append() + .help("GW session file to load (.ini suffix)"); program.add_argument("--start-index") .default_value(0).append().scan<'i', int>() .help("Start labelling from -v / -i index (zero-based)"); @@ -247,6 +250,12 @@ int main(int argc, char *argv[]) { } } + if (program.is_used("--session")) { + iopts.session_file = program.get("--session"); + have_session_file = true; + use_session = true; + } + // check if bam/cram file provided as main argument auto genome = program.get("genome"); if (Utils::endsWith(genome, ".bam") || Utils::endsWith(genome, ".cram")) { @@ -259,7 +268,7 @@ int main(int argc, char *argv[]) { if (iopts.myIni["genomes"].has(genome)) { iopts.genome_tag = genome; genome = iopts.myIni["genomes"][genome]; - } else if (genome.empty() && !program.is_used("--images") && !iopts.ini_path.empty() && !program.is_used("--no-show")) { + } else if (genome.empty() && !program.is_used("--images") && !iopts.ini_path.empty() && !program.is_used("--no-show") && !program.is_used("--session")) { // prompt for genome print_banner(); show_banner = false; diff --git a/src/plot_commands.cpp b/src/plot_commands.cpp index 97a6fa2..ec82dfd 100644 --- a/src/plot_commands.cpp +++ b/src/plot_commands.cpp @@ -105,9 +105,10 @@ namespace Commands { p->redraw = true; p->processed = false; p->imageCache.clear(); + p->imageCacheQueue.clear(); p->filters.clear(); p->target_qname = ""; - for (auto &cl: p->collections) { cl.vScroll = 0; } + for (auto &cl: p->collections) { cl.vScroll = 0; cl.skipDrawingCoverage = false; cl.skipDrawingReads = false;} return Err::NONE; } @@ -223,6 +224,7 @@ namespace Commands { p->processed = false; p->redraw = true; p->imageCache.clear(); + p->imageCacheQueue.clear(); return Err::NONE; } @@ -231,6 +233,7 @@ namespace Commands { p->processed = false; p->redraw = true; p->imageCache.clear(); + p->imageCacheQueue.clear(); return Err::NONE; } @@ -239,6 +242,7 @@ namespace Commands { p->processed = false; p->redraw = true; p->imageCache.clear(); + p->imageCacheQueue.clear(); return Err::NONE; } @@ -247,6 +251,7 @@ namespace Commands { p->processed = false; p->redraw = true; p->imageCache.clear(); + p->imageCacheQueue.clear(); return Err::NONE; } @@ -259,6 +264,7 @@ namespace Commands { } p->redraw = true; p->imageCache.clear(); + p->imageCacheQueue.clear(); return Err::NONE; } @@ -275,6 +281,7 @@ namespace Commands { p->processed = false; } p->imageCache.clear(); + p->imageCacheQueue.clear(); return Err::NONE; } @@ -287,6 +294,7 @@ namespace Commands { p->processed = false; } p->imageCache.clear(); + p->imageCacheQueue.clear(); return Err::NONE; } @@ -324,6 +332,7 @@ namespace Commands { } if (relink) { p->imageCache.clear(); + p->imageCacheQueue.clear(); HGW::refreshLinked(p->collections, p->opts, &p->samMaxY); p->redraw = true; p->processed = true; @@ -416,6 +425,7 @@ namespace Commands { } } p->imageCache.clear(); + p->imageCacheQueue.clear(); p->redraw = true; p->processed = false; return Err::NONE; @@ -485,6 +495,7 @@ namespace Commands { p->processed = false; } p->imageCache.clear(); + p->imageCacheQueue.clear(); } else if (command == "mate add" && p->mode == Manager::Show::SINGLE) { p->regions.push_back(Utils::parseRegion(mate)); p->fetchRefSeq(p->regions.back()); @@ -494,6 +505,7 @@ namespace Commands { p->redraw = true; p->processed = true; p->imageCache.clear(); + p->imageCacheQueue.clear(); } } return Err::NONE; @@ -512,6 +524,7 @@ namespace Commands { p->redraw = true; p->processed = true; p->imageCache.clear(); + p->imageCacheQueue.clear(); return Err::NONE; } @@ -529,6 +542,7 @@ namespace Commands { return Err::NONE; } p->imageCache.clear(); + p->imageCacheQueue.clear(); HGW::refreshLinked(p->collections, p->opts, &p->samMaxY); p->processed = true; p->redraw = true; @@ -551,6 +565,7 @@ namespace Commands { } p->redraw = true; p->imageCache.clear(); + p->imageCacheQueue.clear(); return Err::NONE; } @@ -593,6 +608,8 @@ namespace Commands { if (clear_filters) { p->filters.clear(); } + p->imageCacheQueue.clear(); + p->imageCacheQueue.clear(); return Err::NONE; } @@ -622,6 +639,7 @@ namespace Commands { p->redraw = true; p->processed = false; p->imageCache.clear(); + p->imageCacheQueue.clear(); return Err::NONE; } @@ -631,11 +649,11 @@ namespace Commands { return Err::NONE; } if (parts.back() == "dark") { - p->opts.theme = Themes::DarkTheme(); p->opts.theme.setAlphas(); p->imageCache.clear(); p->opts.theme_str = "dark"; + p->opts.theme = Themes::DarkTheme(); p->opts.theme.setAlphas(); p->imageCache.clear(); p->imageCacheQueue.clear(); p->opts.theme_str = "dark"; } else if (parts.back() == "igv") { - p->opts.theme = Themes::IgvTheme(); p->opts.theme.setAlphas(); p->imageCache.clear(); p->opts.theme_str = "igv"; + p->opts.theme = Themes::IgvTheme(); p->opts.theme.setAlphas(); p->imageCache.clear(); p->imageCacheQueue.clear(); p->opts.theme_str = "igv"; } else if (parts.back() == "slate") { - p->opts.theme = Themes::SlateTheme(); p->opts.theme.setAlphas(); p->imageCache.clear(); p->opts.theme_str = "slate"; + p->opts.theme = Themes::SlateTheme(); p->opts.theme.setAlphas(); p->imageCache.clear(); p->imageCacheQueue.clear(); p->opts.theme_str = "slate"; } else { return Err::OPTION_NOT_UNDERSTOOD; } @@ -709,6 +727,7 @@ namespace Commands { p->redraw = true; p->processed = false; p->imageCache.clear(); + p->imageCacheQueue.clear(); return Err::NONE; } @@ -744,7 +763,9 @@ namespace Commands { p->regions.insert(p->regions.end(), new_regions.begin(), new_regions.end()); p->redraw = true; p->processed = false; + for (auto &cl: p->collections) { cl.skipDrawingCoverage = false; cl.skipDrawingReads = false;} p->imageCache.clear(); + p->imageCacheQueue.clear(); return Err::NONE; } @@ -1005,6 +1026,7 @@ namespace Commands { } if (as_alignments(parts.back())) { if (parts.size() == 2) { + out << "Saved alignments: " << parts.back() << std::endl; write_bam(p, parts.back(), targets, out); } else if (parts.size() == 3) { if (parts[1] == ">") { @@ -1018,6 +1040,14 @@ namespace Commands { return Err::OPTION_NOT_UNDERSTOOD; } } + // Err snapshot(Plot* p, std::vector parts, std::ostream& out) { + else if (Utils::endsWith(command, ".png")) { + return snapshot(p, parts, out); + } + else if (Utils::endsWith(parts.back(), ".ini")) { + out << "Saved session: " << parts.back() << std::endl; + p->saveSession(parts.back()); + } return Err::NONE; } @@ -1080,6 +1110,7 @@ namespace Commands { p->redraw = true; p->processed = false; p->imageCache.clear(); + p->imageCacheQueue.clear(); } return reason; } diff --git a/src/plot_controls.cpp b/src/plot_controls.cpp index 10e6705..dd0bf70 100644 --- a/src/plot_controls.cpp +++ b/src/plot_controls.cpp @@ -70,7 +70,7 @@ namespace Manager { // keeps track of input commands. returning GLFW_KEY_UNKNOWN stops further processing of key codes int GwPlot::registerKey(GLFWwindow* wind, int key, int scancode, int action, int mods) { std::ostream& out = (terminalOutput) ? std::cout : outStr; - if (key == GLFW_KEY_LEFT_CONTROL || key == GLFW_KEY_LEFT_SUPER) { + if (key == GLFW_KEY_LEFT_CONTROL || key == GLFW_KEY_LEFT_SUPER || key == GLFW_KEY_RIGHT_CONTROL || key == GLFW_KEY_RIGHT_SUPER) { if (action == GLFW_PRESS || action == GLFW_REPEAT) { ctrlPress = true; } else if (action == GLFW_RELEASE) { @@ -87,6 +87,10 @@ namespace Manager { } else if (action == GLFW_RELEASE) { return key; } + if (ctrlPress && key == GLFW_KEY_C) { + triggerClose = true; + return GLFW_KEY_UNKNOWN; + } if ( (key == GLFW_KEY_SLASH && !captureText) || (shiftPress && key == GLFW_KEY_SEMICOLON && !captureText)) { captureText = true; @@ -169,6 +173,7 @@ namespace Manager { int step_y = monitor_h / 8; redraw = true; + imageCacheQueue.clear(); if (key == GLFW_KEY_RIGHT) { if (current_x <= 0 && current_w < monitor_w ) { @@ -288,6 +293,7 @@ namespace Manager { redraw = true; processed = true; imageCache.clear(); + imageCacheQueue.clear(); commandToolTipIndex = -1; out << "\n"; return GLFW_KEY_ENTER; @@ -421,6 +427,7 @@ namespace Manager { redraw = true; inputText = ""; imageCache.clear(); + imageCacheQueue.clear(); } void GwPlot::removeTrack(int index) { @@ -442,6 +449,7 @@ namespace Manager { redraw = true; inputText = ""; imageCache.clear(); + imageCacheQueue.clear(); } void GwPlot::removeRegion(int index) { @@ -464,6 +472,7 @@ namespace Manager { redraw = true; inputText = ""; imageCache.clear(); + imageCacheQueue.clear(); } void GwPlot::highlightQname() { @@ -502,6 +511,7 @@ namespace Manager { } processText = false; // text will be processed by run_command_map std::ostream& out = (terminalOutput) ? std::cout : outStr; + glfwSetCursor(window, normalCursor); Commands::run_command_map(this, inputText, out); return true; } @@ -605,6 +615,7 @@ namespace Manager { redraw = true; processed = false; imageCache.clear(); + imageCacheQueue.clear(); inputText = ""; opts.editing_underway = false; textFromSettings = false; @@ -993,6 +1004,7 @@ namespace Manager { std::string lk = (opts.link_op > 0) ? ((opts.link_op == 1) ? "sv" : "all") : "none"; out << "\nLinking selection " << lk << std::endl; imageCache.clear(); + imageCacheQueue.clear(); HGW::refreshLinked(collections, opts, &samMaxY); redraw = true; } @@ -1052,6 +1064,11 @@ namespace Manager { std::string pth = *paths; addTrack(pth); } + redraw = true; + processed = false; + imageCache.clear(); + imageCacheQueue.clear(); + for (auto &cl: collections) { cl.skipDrawingCoverage = false; cl.skipDrawingReads = false;} } int GwPlot::getCollectionIdx(float x, float y) { @@ -1326,7 +1343,6 @@ namespace Manager { slop = (int)(max_bound * 0.025); slop = (slop <= 0) ? 25 : slop; } - std::cout << " level " << level << std::endl; std::vector::iterator bnd; bnd = std::lower_bound(cl.readQueue.begin(), cl.readQueue.end(), pos, [&](const Segs::Align &lhs, const int pos) { return (int)lhs.pos <= pos; }); @@ -1351,7 +1367,7 @@ namespace Manager { redraw = true; processed = true; cl.skipDrawingReads = false; - cl.skipDrawingCoverage = true; + cl.skipDrawingCoverage = false; break; } } else { @@ -1374,7 +1390,7 @@ namespace Manager { redraw = true; processed = true; cl.skipDrawingReads = false; - cl.skipDrawingCoverage = true; + cl.skipDrawingCoverage = false; } } if (bnd == cl.readQueue.begin()) { @@ -1731,6 +1747,11 @@ namespace Manager { float new_boundary = fb_height - yPos_fb - refSpace; opts.tab_track_height = new_boundary / drawingArea; redraw = true; + for (auto & cl: collections) { + cl.skipDrawingCoverage = false; + cl.skipDrawingReads = false; + } + imageCacheQueue.clear(); return; } } diff --git a/src/plot_manager.cpp b/src/plot_manager.cpp index a8d52e4..fc48512 100644 --- a/src/plot_manager.cpp +++ b/src/plot_manager.cpp @@ -514,8 +514,8 @@ namespace Manager { commandProcessed(); } } - if (opts.seshIni["show"].has("window_position")) { - Utils::Dims pos = Utils::parseDimensions(opts.seshIni["show"]["window_position"]); + if (opts.seshIni["general"].has("window_position")) { + Utils::Dims pos = Utils::parseDimensions(opts.seshIni["general"]["window_position"]); glfwSetWindowPos(window, pos.x, pos.y); } glfwSetWindowSize(window, opts.dimensions.x, opts.dimensions.y); @@ -523,7 +523,7 @@ namespace Manager { redraw = true; } - void GwPlot::saveSession() { + void GwPlot::saveSession(std::string output_session="") { std::vector track_paths; for (const auto& item: tracks) { if (item.kind != HGW::FType::ROI) { @@ -539,7 +539,7 @@ namespace Manager { int windX, windY; glfwGetWindowSize(window, &windX, &windY); opts.saveCurrentSession(reference, ideogram_path, bam_paths, track_paths, regions, variant_paths_info, commandsApplied, - "", mode, xpos, ypos, monitorScale, windX, windY); + output_session, mode, xpos, ypos, monitorScale, windX, windY); } int GwPlot::startUI(GrDirectContext* sContext, SkSurface *sSurface, int delay) { @@ -584,13 +584,7 @@ namespace Manager { if (redraw) { if (mode == Show::SINGLE) { -// if (opts.low_mem) { -// drawScreenNoBuffer(sSurface->getCanvas(), sContext, sSurface); -// } else { - drawScreen(sSurface->getCanvas(), sContext, sSurface); -// } - } else if (mode == Show::TILED) { drawTiles(sSurface->getCanvas(), sContext, sSurface); printIndexInfo(); @@ -641,15 +635,6 @@ namespace Manager { std::exit(-1); } -// SkImageInfo info = SkImageInfo::MakeN32Premul(fb_width, fb_height); -// size_t rowBytes = info.minRowBytes(); -// size_t size = info.computeByteSize(rowBytes); -// pixelMemory.resize(size); -// rasterSurface = SkSurface::MakeRasterDirect( -// info, &pixelMemory[0], rowBytes); -// rasterCanvas = rasterSurface->getCanvas(); - - rasterSurface = SkSurface::MakeRasterN32Premul(fb_width,fb_height); rasterCanvas = rasterSurface->getCanvas(); rasterSurfacePtr = &rasterSurface; @@ -690,28 +675,22 @@ namespace Manager { const int parse_mods_threshold = (opts.parse_mods) ? opts.mods_qual_threshold: 0; if (!processed) { int idx = 0; - if (collections.size() != bams.size() * regions.size()) { - for (auto &cl: collections) { - for (auto &aln: cl.readQueue) { - bam_destroy1(aln.delegate); - } + + for (auto &cl: collections) { + for (auto &aln : cl.readQueue) { + bam_destroy1(aln.delegate); } - collections.clear(); + cl.readQueue.clear(); + cl.covArr.clear(); + cl.mmVector.clear(); + cl.levelsStart.clear(); + cl.levelsEnd.clear(); + cl.linked.clear(); + cl.skipDrawingReads = false; + cl.skipDrawingCoverage = false; + } + if (collections.size() != bams.size() * regions.size()) { collections.resize(bams.size() * regions.size()); - } else { - for (auto &cl: collections) { - for (auto &aln : cl.readQueue) { - bam_destroy1(aln.delegate); - } - cl.readQueue.clear(); - cl.covArr.clear(); - cl.mmVector.clear(); - cl.levelsStart.clear(); - cl.levelsEnd.clear(); - cl.linked.clear(); - cl.skipDrawingReads = false; - cl.skipDrawingCoverage = false; - } } for (int i=0; i<(int)bams.size(); ++i) { @@ -828,6 +807,7 @@ namespace Manager { cl.xOffset = (regionWidth * (float)cl.regionIdx) + gap; cl.yOffset = (float)cl.bamIdx * bamHeight + covY + refSpace; cl.yPixels = trackY + covY; + cl.xPixels = regionWidth; cl.regionLen = cl.region->end - cl.region->start; cl.regionPixels = cl.regionLen * cl.xScaling; @@ -875,26 +855,30 @@ namespace Manager { processBam(); setScaling(); + if (!imageCacheQueue.empty() && collections.size() > 1) { + canvasR->drawImage(imageCacheQueue.back().second, 0, 0); + } + SkRect clip; + for (auto &cl: collections) { + if (cl.skipDrawingCoverage && cl.skipDrawingReads) { // keep read and coverage area + continue; + } canvasR->save(); - - // Copy some of the image from the last frame - if ((cl.skipDrawingCoverage || cl.skipDrawingReads) && !imageCacheQueue.empty()) { - if (cl.skipDrawingCoverage) { - canvasR->clipRect({cl.xOffset, cl.yOffset - covY, (float)cl.regionLen * cl.xScaling + cl.xOffset, cl.yOffset + covY}, false); - canvasR->drawImage(imageCacheQueue.back().second, 0, 0); - canvasR->restore(); - canvasR->save(); - canvasR->clipRect({cl.xOffset, cl.yOffset, (float)cl.regionLen * cl.xScaling + cl.xOffset, cl.yOffset + trackY}, false); + // 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 { - canvasR->clipRect({cl.xOffset, cl.yOffset - covY, (float)cl.regionLen * cl.xScaling + cl.xOffset, cl.yOffset + covY}, false); - canvasR->drawImage(imageCacheQueue.back().second, 0, 0); - canvasR->restore(); - canvasR->save(); - canvasR->clipRect({cl.xOffset, cl.yOffset - covY, (float)cl.regionLen * cl.xScaling + cl.xOffset, cl.yOffset + covY}, false); + clip.setXYWH(cl.xOffset, cl.yOffset - covY, cl.regionPixels, cl.yOffset + trackY + covY); } - } else { // whole frame - canvasR->clipRect({cl.xOffset, cl.yOffset - covY, (float)cl.regionLen * cl.xScaling + cl.xOffset, cl.yOffset + trackY + covY}, false); + canvasR->clipRect(clip, false); + } else if (cl.skipDrawingCoverage) { + clip.setXYWH(cl.xOffset, cl.yOffset + refSpace, cl.regionPixels, cl.yPixels - covY); + canvasR->clipRect(clip, false); + } else { // skip reads + clip.setXYWH(cl.xOffset, cl.yOffset - covY, cl.regionPixels, covY); + canvasR->clipRect(clip, false); } canvasR->drawPaint(opts.theme.bgPaint); @@ -902,7 +886,6 @@ namespace Manager { if (cl.regionLen >= opts.low_memory && !bams.empty() && opts.link_op == 0) { // low memory mode will be used cl.clear(); -// std::cout << " iter draw\n"; if (opts.threads == 1) { HGW::iterDraw(cl, bams[cl.bamIdx], headers[cl.bamIdx], indexes[cl.bamIdx], ®ions[cl.regionIdx], (bool) opts.max_coverage, @@ -915,7 +898,6 @@ namespace Manager { pointSlop, textDrop, pH, monitorScale); } } else { -// std::cout << " full draw\n"; Drawing::drawCollection(opts, cl, canvasR, trackY, yScaling, fonts, opts.link_op, refSpace, pointSlop, textDrop, pH, monitorScale); } @@ -923,7 +905,6 @@ namespace Manager { canvasR->restore(); } -// std::cout << " done draw\n"; if (opts.max_coverage) { Drawing::drawCoverage(opts, collections, canvasR, fonts, covY, refSpace); } @@ -931,9 +912,6 @@ namespace Manager { Drawing::drawBorders(opts, fb_width, fb_height, canvasR, regions.size(), bams.size(), trackY, covY, (int)tracks.size(), totalTabixY, refSpace, gap); Drawing::drawTracks(opts, fb_width, fb_height, canvasR, totalTabixY, tabixY, tracks, regions, fonts, gap, monitorScale); Drawing::drawChromLocation(opts, regions, ideogram, canvasR, fai, fb_width, fb_height, monitorScale); -// if (mode == Show::SINGLE) { -// drawCursorPosOnRefSlider(canvas); -// } } diff --git a/src/plot_manager.h b/src/plot_manager.h index 485d710..9112841 100644 --- a/src/plot_manager.h +++ b/src/plot_manager.h @@ -123,14 +123,13 @@ namespace Manager { std::vector filters; - std::unordered_map< int, sk_sp> imageCache; + std::unordered_map< int, sk_sp> imageCache; // Cace of tiled images + std::deque< std::pair > > imageCacheQueue; // cache of previously draw main screen images // keys are variantFilename and variantId ankerl::unordered_dense::map< std::string, ankerl::unordered_dense::map< std::string, Utils::Label>> inputLabels; ankerl::unordered_dense::map< std::string, ankerl::unordered_dense::set> seenLabels; - std::deque< std::pair > > imageCacheQueue; - Themes::IniOptions opts; Themes::Fonts fonts; @@ -227,7 +226,7 @@ namespace Manager { void highlightQname(); - void saveSession(); + void saveSession(std::string out_session); private: long frameId; diff --git a/src/segments.cpp b/src/segments.cpp index fc85e9a..1b1423b 100644 --- a/src/segments.cpp +++ b/src/segments.cpp @@ -898,10 +898,18 @@ namespace Segs { // first find reads that should be linked together using qname if (linkType > 0) { lm.clear(); - q_ptr = &rc.readQueue.front(); + q_ptr = &rQ.front(); // find the start and end coverage locations of aligns with same name - for (i=0; i < (int)rc.readQueue.size(); ++i) { + for (i=0; i < (int)rQ.size(); ++i) { + if (!(q_ptr->delegate->core.flag & 1)) { + ++q_ptr; + continue; + } qname = bam_get_qname(q_ptr->delegate); + if (qname == nullptr) { + ++q_ptr; + continue; + } if (linkType == 1) { uint32_t flag = q_ptr->delegate->core.flag; if (q_ptr->has_SA || ~flag & 2) { @@ -982,7 +990,7 @@ namespace Segs { } if (linkType > 0) { qname = bam_get_qname(q_ptr->delegate); - if (linkedSeen.find(qname) != linkedSeen.end()) { + if (qname != nullptr && linkedSeen.find(qname) != linkedSeen.end()) { q_ptr->y = linkedSeen[qname]; q_ptr += move; continue; @@ -998,13 +1006,13 @@ namespace Segs { if (i >= vScroll) { q_ptr->y = i - vScroll; } - if (linkType > 0 && lm.find(qname) != lm.end()) { + if (linkType > 0 && qname != nullptr && lm.find(qname) != lm.end()) { linkedSeen[qname] = q_ptr->y; } break; } } - if (i == memLen && linkType > 0 && lm.find(qname) != lm.end()) { + if (i == memLen && linkType > 0 && qname != nullptr && lm.find(qname) != lm.end()) { linkedSeen[qname] = q_ptr->y; // y is out of range i.e. -1 } q_ptr += move; @@ -1019,13 +1027,13 @@ namespace Segs { if (i >= vScroll) { q_ptr->y = i - vScroll; } - if (linkType > 0 && lm.find(qname) != lm.end()) { + if (linkType > 0 && qname != nullptr && lm.find(qname) != lm.end()) { linkedSeen[qname] = q_ptr->y; } break; } } - if (i == memLen && linkType > 0 && lm.find(qname) != lm.end()) { + if (i == memLen && linkType > 0 && qname != nullptr && lm.find(qname) != lm.end()) { linkedSeen[qname] = q_ptr->y; // y is out of range i.e. -1 } q_ptr += move; diff --git a/src/segments.h b/src/segments.h index e4243d7..d49f662 100644 --- a/src/segments.h +++ b/src/segments.h @@ -106,7 +106,7 @@ namespace Segs { std::vector mmVector; std::vector readQueue; map_t linked; - float xScaling, xOffset, yOffset, yPixels; + float xScaling, xOffset, yOffset, yPixels, xPixels; float regionPixels; bool collection_processed; diff --git a/src/term_out.cpp b/src/term_out.cpp index f8b833c..f711b94 100644 --- a/src/term_out.cpp +++ b/src/term_out.cpp @@ -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 (.xml) to file\n"; + out << termcolor::green << "save filename " << termcolor::reset << "Save reads (.bam/.cram), snapshot (.png) or session (.ini) to file\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"; @@ -99,6 +99,7 @@ namespace Term { " Elements are selected by name (see below) and values are in the range [0, 255].\n" " For example 'colour fcNormal 255 255 0 0' sets face-colour of normal reads to red.\n\n" " bgPaint - background paint\n" + " bgMenu - background of menu\n" " fcNormal - face-colour normal reads\n" " fcDel - face-colour deletion pattern reads\n" " fcDup - face-colour duplication pattern reads\n" @@ -124,6 +125,8 @@ namespace Term { " fcIns0 - face-colour insertion blocks with mapq=0\n" " fcSoftClips0 - face-colour soft-clips when zoomed-out with mapq=0\n" " fcMarkers - face-colour of markers\n" + " fc5mc - face-colour of 5-Methylcytosine\n" + " fc5hmc - face-colour of 5-Hydroxymethylcytosine\n" " ecMateUnmapped - edge-colour mate unmapped reads\n" " ecSplit - edge-colour split reads\n" " ecSelected - edge-colour selected reads\n" @@ -224,7 +227,7 @@ namespace Term { " 'save reads.cram' # Reads saved in cram format\n" " 'save reads.sam' # Reads saved in sam format (human readable)\n" " 'save view.png' # The current view is saved to view.png. Same functionality as 'snapshot'\n" - " 'save session.xml' # 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\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" @@ -775,6 +778,9 @@ namespace Term { uint8_t *ptr_seq = bam_get_seq(align.delegate); uint32_t *cigar_p = bam_get_cigar(align.delegate); if (cigar_p == nullptr || cigar_l == 0) { + if (bnd == cl.readQueue.begin()) { + break; + } --bnd; continue; } diff --git a/src/themes.cpp b/src/themes.cpp index 425d67c..c1351a2 100644 --- a/src/themes.cpp +++ b/src/themes.cpp @@ -77,7 +77,8 @@ namespace Themes { ecMateUnmapped.setStyle(SkPaint::kStroke_Style); ecMateUnmapped.setStrokeWidth(1); - fcIns.setARGB(255, 178, 77, 255); + fcIns.setStyle(SkPaint::kFill_Style); + //fcIns.setARGB(255, 178, 77, 255); lwMateUnmapped = 0.5; @@ -140,9 +141,6 @@ namespace Themes { lcBright.setStrokeWidth(2); lcBright.setAntiAlias(true); - insF = fcIns; - insF.setStyle(SkPaint::kFill_Style); - fcMarkers.setStyle(SkPaint::kStrokeAndFill_Style); fcMarkers.setAntiAlias(true); fcMarkers.setStrokeMiter(0.1); @@ -252,8 +250,9 @@ namespace Themes { fcC.setARGB(255, 66, 127, 255); fcG.setARGB(255, 235, 150, 23); fcN.setARGB(255, 128, 128, 128); -// fc5mc.setARGB(127, 255, 94, 0); - fc5mc.setARGB(127, 30, 176, 230); + fcIns.setARGB(255, 158, 112, 250); + + fc5mc.setARGB(127, 194, 151, 58); fc5hmc.setARGB(127, 52, 255, 96); lcJoins.setARGB(255, 80, 80, 80); lcLightJoins.setARGB(255, 140, 140, 140); @@ -290,6 +289,7 @@ namespace Themes { fcC.setARGB(255, 77, 125, 245); fcG.setARGB(255, 226, 132, 19); fcN.setARGB(255, 128, 128, 128); + fcIns.setARGB(255, 158, 112, 250); // fc5mc.setARGB(127, 252, 186, 3); fc5mc.setARGB(127, 30, 176, 230); fc5hmc.setARGB(127, 52, 255, 96); @@ -752,7 +752,7 @@ namespace Themes { seshIni["show"]["var" + std::to_string(count)] = std::to_string(item.second); count += 1; } - seshIni["show"]["window_position"] = std::to_string(window_x_pos) + "x" + std::to_string(window_y_pos); + count = 0; size_t last_refresh = 0; std::vector keep = {"egdes", "expand-tracks", "soft-clips", "sc", "mm", "mismatches", "ins", "insertions", "mods"}; @@ -780,7 +780,7 @@ namespace Themes { mINI::INIMap& sub = seshIni["general"]; sub["theme"] = theme_str; sub["dimensions"] = std::to_string(screen_width) + "x" + std::to_string(screen_height); - + sub["window_position"] = std::to_string(window_x_pos) + "x" + std::to_string(window_y_pos); sub["ylim"] = std::to_string(ylim); sub["coverage"] = (max_coverage) ? "true" : "false"; sub["log2_cov"] = (log2_cov) ? "true" : "false"; diff --git a/src/themes.h b/src/themes.h index ef663cf..3653b68 100644 --- a/src/themes.h +++ b/src/themes.h @@ -96,7 +96,7 @@ namespace Themes { float lwMateUnmapped, lwSplit, lwCoverage; // line colours and Insertion paint - SkPaint lcJoins, lcCoverage, lcLightJoins, insF, lcLabel, lcBright; + SkPaint lcJoins, lcCoverage, lcLightJoins, lcLabel, lcBright; // text colours SkPaint tcDel, tcIns, tcLabels, tcBackground; From 9a3c3e0454c20beb1f34ba63e6433f4cb46c2bf2 Mon Sep 17 00:00:00 2001 From: kcleal Date: Mon, 8 Jul 2024 13:04:06 +0100 Subject: [PATCH 50/51] Work on print function. Nicer ins drawing --- src/drawing.cpp | 2 + src/term_out.cpp | 107 +++++++++++++++++++++++++++++++++-------------- src/themes.cpp | 9 ++-- 3 files changed, 84 insertions(+), 34 deletions(-) diff --git a/src/drawing.cpp b/src/drawing.cpp index ea7c300..6502128 100644 --- a/src/drawing.cpp +++ b/src/drawing.cpp @@ -1018,6 +1018,8 @@ namespace Drawing { mmPosOffset = 0.05; mmScaling = 0.9; } else { + + mmPosOffset = 0; mmScaling = 1; } diff --git a/src/term_out.cpp b/src/term_out.cpp index f711b94..6fe3edd 100644 --- a/src/term_out.cpp +++ b/src/term_out.cpp @@ -347,6 +347,7 @@ namespace Term { int i = 0; int p = r->pos; bool started = false; + bool done_final_op = false; int printed = 0; for (k = 0; k < cigar_l; k++) { op = cigar_p[k] & BAM_CIGAR_MASK; @@ -396,8 +397,12 @@ namespace Term { } p += l; if (printed > max) { - out << "..."; - break; + if (done_final_op) { + out << "..."; + break; + } else { + done_final_op = true; + } } } else if (op == BAM_CMATCH) { @@ -460,15 +465,20 @@ namespace Term { } } else { if (mod_it != mod_end && i == mod_it->index) { - if (*target_mod == 'm') { - out << termcolor::on_yellow << termcolor::grey; - } else if (*target_mod == 'h') { - out << termcolor::on_green << termcolor::grey; + for (int j=0; j < mod_it->n_mods; ++j) { + if (mod_it->mods[j] == *target_mod) { + if (*target_mod == 'm') { + out << termcolor::on_yellow << termcolor::grey; + } else if (*target_mod == 'h') { + out << termcolor::on_green << termcolor::grey; + } + out << "M" << termcolor::reset; + } } - out << "M" << termcolor::reset; + ++mod_it; } else { - out << "."; + out << "_"; } } @@ -479,9 +489,13 @@ namespace Term { p += 1; } - if (printed > max) { - out << "..."; - break; + if (printed > max*2) { + if (done_final_op) { + out << "..."; + break; + } else { + done_final_op = true; + } } } else if (op == BAM_CEQUAL) { @@ -518,15 +532,21 @@ namespace Term { } } else { if (mod_it != mod_end && i == mod_it->index) { - if (*target_mod == 'm') { - out << termcolor::on_yellow << termcolor::grey; - } else if (*target_mod == 'h') { - out << termcolor::on_green << termcolor::grey; + + for (int j=0; j < mod_it->n_mods; ++j) { + if (mod_it->mods[j] == *target_mod) { + if (*target_mod == 'm') { + out << termcolor::on_yellow << termcolor::grey; + } else if (*target_mod == 'h') { + out << termcolor::on_green << termcolor::grey; + } + out << "=" << termcolor::reset; + } } - out << "=" << termcolor::reset; + ++mod_it; } else { - out << "."; + out << "_"; } } i += 1; @@ -535,11 +555,16 @@ namespace Term { } // p += l; if (printed > max / 2) { - out << "..."; - break; + if (done_final_op) { + out << "..."; + break; + } else { + done_final_op = true; + } } } else if (op == BAM_CDIFF || op == BAM_CINS) { + if (started || std::abs(pos - p) < max / 2 || std::abs(pos - block_end) < max / 2 || overlaps) { started = true; } else { @@ -549,7 +574,7 @@ namespace Term { i += l; continue; } - printed += l; + if (op == BAM_CINS && l > indel_length) { out << termcolor::magenta << "[" << std::to_string(l) << "]" << termcolor::reset; } @@ -577,25 +602,36 @@ namespace Term { } else { if (mod_it != mod_end && i == mod_it->index) { char o = (op == BAM_CINS) ? 'I' : 'X'; - if (*target_mod == 'm') { - out << termcolor::on_yellow << termcolor::grey; - } else if (*target_mod == 'h') { - out << termcolor::on_green << termcolor::grey; + + for (int j=0; j < mod_it->n_mods; ++j) { + if (mod_it->mods[j] == *target_mod) { + if (*target_mod == 'm') { + out << termcolor::on_yellow << termcolor::grey; + } else if (*target_mod == 'h') { + out << termcolor::on_green << termcolor::grey; + } + out << o << termcolor::reset; + } } - out << o << termcolor::reset; + ++mod_it; } else { - out << "."; + out << "_"; } } i += 1; } if (op == BAM_CDIFF) { p += l; + printed += l; } if (printed > max) { - out << "..."; - break; + if (done_final_op) { + out << "..."; + break; + } else { + done_final_op = true; + } } } else { // soft-clips @@ -640,10 +676,19 @@ namespace Term { } } else { if (mod_it != mod_end && i == mod_it->index) { - out << "S"; + for (int j=0; j < mod_it->n_mods; ++j) { + if (mod_it->mods[j] == *target_mod) { + if (*target_mod == 'm') { + out << termcolor::on_yellow << termcolor::grey; + } else if (*target_mod == 'h') { + out << termcolor::on_green << termcolor::grey; + } + out << "S" << termcolor::reset; + } + } ++mod_it; } else { - out << "."; + out << "_"; } } i += 1; @@ -670,7 +715,7 @@ namespace Term { const char *rname = sam_hdr_tid2name(hdr, r->delegate->core.tid); const char *rnext = sam_hdr_tid2name(hdr, r->delegate->core.mtid); - int term_width = std::max(Utils::get_terminal_width() * 2 - 9, 50); + int term_width = std::max(Utils::get_terminal_width() - 9, 50); out << std::endl << std::endl; out << termcolor::bold << "qname " << termcolor::reset << bam_get_qname(r->delegate) << std::endl; diff --git a/src/themes.cpp b/src/themes.cpp index c1351a2..8880643 100644 --- a/src/themes.cpp +++ b/src/themes.cpp @@ -253,7 +253,8 @@ namespace Themes { fcIns.setARGB(255, 158, 112, 250); fc5mc.setARGB(127, 194, 151, 58); - fc5hmc.setARGB(127, 52, 255, 96); +// fc5hmc.setARGB(127, 52, 255, 96); + fc5hmc.setARGB(127, 189, 78, 23); lcJoins.setARGB(255, 80, 80, 80); lcLightJoins.setARGB(255, 140, 140, 140); lcLabel.setARGB(255, 80, 80, 80); @@ -292,7 +293,8 @@ namespace Themes { fcIns.setARGB(255, 158, 112, 250); // fc5mc.setARGB(127, 252, 186, 3); fc5mc.setARGB(127, 30, 176, 230); - fc5hmc.setARGB(127, 52, 255, 96); +// fc5hmc.setARGB(127, 52, 255, 96); + fc5hmc.setARGB(127, 215, 85, 23); lcJoins.setARGB(255, 142, 142, 142); lcLightJoins.setARGB(255, 82, 82, 82); lcLabel.setARGB(255, 182, 182, 182); @@ -331,7 +333,8 @@ namespace Themes { fcN.setARGB(255, 128, 128, 128); // fc5mc.setARGB(127, 252, 186, 3); fc5mc.setARGB(127, 30, 176, 230); - fc5hmc.setARGB(127, 52, 255, 96); +// fc5hmc.setARGB(127, 52, 255, 96); + fc5hmc.setARGB(127, 215, 85, 23); lcJoins.setARGB(255, 142, 142, 142); lcLightJoins.setARGB(255, 82, 82, 82); lcLabel.setARGB(255, 182, 182, 182); From d4f52d61e38b35c232b92bce3b836d8df0ac89e0 Mon Sep 17 00:00:00 2001 From: kcleal Date: Mon, 8 Jul 2024 13:06:59 +0100 Subject: [PATCH 51/51] Gw v0.10.0 --- .github/workflows/main.yml | 2 +- deps/gw.desktop | 2 +- docs/docs/install/Linux.md | 2 +- docs/docs/install/MacOS.md | 2 +- docs/docs/install/Windows.md | 2 +- src/main.cpp | 2 +- src/menu.cpp | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 83d62ef..95586aa 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -5,7 +5,7 @@ on: pull_request: env: - version: 0.9.3 + version: 0.10.0 jobs: mingw: diff --git a/deps/gw.desktop b/deps/gw.desktop index 3d32140..2a072a3 100644 --- a/deps/gw.desktop +++ b/deps/gw.desktop @@ -1,6 +1,6 @@ [Desktop Entry] Encoding=UTF-8 -Version=0.9.3 +Version=0.10.0 Type=Application Terminal=true Exec=bash -c "/usr/bin/gw" diff --git a/docs/docs/install/Linux.md b/docs/docs/install/Linux.md index ca13b8e..140e71a 100644 --- a/docs/docs/install/Linux.md +++ b/docs/docs/install/Linux.md @@ -11,7 +11,7 @@ For best performance, install GW as a desktop application on (Intel debian syste Use the installer below, or head over to the GitHub [Releases page](https://github.com/kcleal/gw/releases). -[GW Intel x86_64 debian installer](https://github.com/kcleal/gw/releases/download/v0.9.3/gw_0.9.3_amd64.deb) +[GW Intel x86_64 debian installer](https://github.com/kcleal/gw/releases/download/v0.10.0/gw_0.10.0_amd64.deb) diff --git a/docs/docs/install/MacOS.md b/docs/docs/install/MacOS.md index e119300..714d00d 100644 --- a/docs/docs/install/MacOS.md +++ b/docs/docs/install/MacOS.md @@ -11,7 +11,7 @@ For best performance, install GW as a desktop application (Intel or Apple silico installers below, or head over to the GitHub [Releases page](https://github.com/kcleal/gw/releases). -[GW Intel x86_64 mac dmg installer](https://github.com/kcleal/gw/releases/download/v0.9.3/gw_macos_intel.dmg) +[GW Intel x86_64 mac dmg installer](https://github.com/kcleal/gw/releases/download/v0.10.0/gw_macos_intel.dmg) [GW Apple arm64 mac dmg installer](https://github.com/kcleal/gw/releases/download/v0.9.3/gw-macos-arm.dmg) diff --git a/docs/docs/install/Windows.md b/docs/docs/install/Windows.md index f664607..c2be9c8 100644 --- a/docs/docs/install/Windows.md +++ b/docs/docs/install/Windows.md @@ -25,7 +25,7 @@ Follow the installation instructions on the website to set up MSYS2 on your Wind Download the GW installer script below: -[GW Intel x86_64 Windows installer](https://github.com/kcleal/gw/releases/download/v0.9.3/gw-windows-installer.vbs) +[GW Intel x86_64 Windows installer](https://github.com/kcleal/gw/releases/download/v0.10.0/gw-windows-installer.vbs) Run the downloaded visual-basic script by double-clicking, or right-clicking and selecting Run as program. diff --git a/src/main.cpp b/src/main.cpp index 0b33774..1798cbc 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.9.3"); + argparse::ArgumentParser program("gw", "0.10.0"); program.add_argument("genome") .default_value(std::string{""}).append() diff --git a/src/menu.cpp b/src/menu.cpp index 05a5a32..01532a4 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.9.3"; + tip = opts.ini_path + " v0.10.0"; } 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"; }