diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0c5b16d..95586aa 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -5,14 +5,14 @@ on: pull_request: env: - version: 0.9.3 + version: 0.10.0 jobs: mingw: strategy: fail-fast: false matrix: - sys: [clang64, ucrt64] + sys: [ucrt64] # clang64, name: ${{ matrix.sys }} runs-on: windows-latest defaults: @@ -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 @@ -140,9 +140,10 @@ 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 + sed -i 's/ -g -Wall/ -Wall/' Makefile # strip debug information ./configure --enable-libcurl --enable-s3 --enable-lzma --enable-bz2 --with-libdeflate make -j3 echo "HTSLIB BUILT" && pwd @@ -164,21 +165,24 @@ jobs: - name: package run: | - 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'" + 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%%:\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 + dpkg-buildpackage -us -uc + - name: upload uses: actions/upload-artifact@v3 with: diff --git a/.gitignore b/.gitignore index a43cd25..1b8179d 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,9 @@ /lib/libBigWig/bwValues.o /lib/libBigWig/bwWrite.o /lib/libBigWig/io.o +/lib/libgw/ +libgw.so +/libgw.dylib +/gw.wasm +/wasm_libs +/src/plot_commands.o diff --git a/.gw.ini b/.gw.ini index 49180f5..a2e1ae8 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,12 +37,16 @@ tabix_track_height=0.3 font=Menlo font_size=14 sv_arcs=true +mods=false +mods_qual_threshold=50 +session_file= [view_thresholds] soft_clip=20000 small_indel=100000 snp=500000 -edge_highlights=100000 +mod=250000 +edge_highlights=1000000 variant_distance=100000 low_memory=1500000 diff --git a/Makefile b/Makefile index ca0066d..ec09315 100644 --- a/Makefile +++ b/Makefile @@ -9,11 +9,13 @@ 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) + ifdef EMSCRIPTEN + PLATFORM = "Emscripten" + else ifeq ($(UNAME_S),Linux) PLATFORM = "Linux" else ifeq ($(UNAME_S),Darwin) ifeq ($(shell uname -m), arm64) @@ -23,36 +25,38 @@ 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 ?= "" +SKIA_PATH="" ifneq ($(PLATFORM), "Windows") ifneq ($(SKIA),"") CPPFLAGS += -I$(SKIA) - LDFLAGS += -L $(wildcard $(SKIA)/out/Rel*) - else ifeq ($(PLATFORM),"Darwin") - CPPFLAGS += -I./lib/skia - LDFLAGS += -L./lib/skia/out/Release-x64 + SKIA_PATH = $(wildcard $(SKIA)/out/Rel*) else ifeq ($(PLATFORM),"Arm64") CPPFLAGS += -I./lib/skia - LDFLAGS += -L./lib/skia/out/Release-arm64 - else + SKIA_PATH = ./lib/skia/out/Release-arm64 + 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 - LDFLAGS += -L./lib/skia/out/Release-x64 + SKIA_PATH = ./lib/skia/out/Release-x64 + LDFLAGS= endif endif +LDFLAGS += -L$(SKIA_PATH) SKIA_LINK="" USE_GL ?= "" # Else use EGL backend for Linux only @@ -60,29 +64,24 @@ 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 - -CXXFLAGS += -Wall -std=c++17 -fno-common -fwrapv -fno-omit-frame-pointer -O3 -DNDEBUG - -CPPFLAGS += -I./lib/libBigWig -I./include -I./src -I. - -LDLIBS += -lskia -lm -ljpeg -lpng -lsvg -lhts -lfontconfig -lpthread - - - -# set platform flags and libs -ifeq ($(PLATFORM),"Linux") +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 +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 else @@ -92,60 +91,72 @@ ifeq ($(PLATFORM),"Linux") 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=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 - + 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=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 - + 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)) OBJECTS += $(patsubst %.c, %.o, $(wildcard ./lib/libBigWig/*.c)) -debug: CXXFLAGS += -g debug: LDFLAGS += -fsanitize=address -fsanitize=undefined -$(TARGET): $(OBJECTS) - $(CXX) -g $(OBJECTS) $(LDFLAGS) $(LDLIBS) -o $@ +$(TARGET): $(OBJECTS) # line 131 + $(CXX) $(CXXFLAGS) $(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 -SHARED_TARGET = libgw/libgw.so -ifeq ($(PLATFORM),"Darwin") - SHARED_TARGET = libgw/libgw.dylib + +ifeq ($(UNAME_S),Linux) + SHARED_TARGET = libgw.so +endif +ifeq ($(UNAME_S),Darwin) + SHARED_TARGET = libgw.dylib endif -shared: CXXFLAGS += -fPIC +shared: CXXFLAGS += -fPIC -DBUILDING_LIBGW +shared: CFLAGS += -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) + +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 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/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/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/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/include/export_definitions.h b/include/export_definitions.h new file mode 100644 index 0000000..65be0a1 --- /dev/null +++ b/include/export_definitions.h @@ -0,0 +1,35 @@ +// +// Created by Kez Cleal on 23/02/2024. +// + +#pragma once + +#ifdef __EMSCRIPTEN__ + #include +#endif + +#if defined(__GNUC__) && __GNUC__ >= 4 + #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 + // 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/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/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/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 96d205e..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 @@ -14,9 +17,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/lib/libgw/test/Makefile b/lib/libgw/test/Makefile new file mode 100644 index 0000000..7a671e8 --- /dev/null +++ b/lib/libgw/test/Makefile @@ -0,0 +1,25 @@ + +CXXFLAGS += -std=c++17 -DOSX -stdlib=libc++ -arch arm64 -mmacosx-version-min=11 +CPPFLAGS += -I../include -I../../skia/ -I../../libBigWig +LDFLAGS += -L../out -undefined dynamic_lookup -framework OpenGL -framework AppKit -framework ApplicationServices +LDLIBS += -lskia -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 $@ + -install_name_tool -change libgw.dylib @loader_path/../out/libgw.dylib hello + +clean: + -rm -rf *.o ./*.o ./*.dSYM + -rm -f hello \ No newline at end of file diff --git a/lib/libgw/test/test.cpp b/lib/libgw/test/test.cpp new file mode 100644 index 0000000..b0aaf16 --- /dev/null +++ b/lib/libgw/test/test.cpp @@ -0,0 +1,11 @@ +// +// Created by Kez Cleal on 29/02/2024. +// + +#include +#include "libgwplot.hpp" + +int main(int argc, char *argv[]) { + Themes::BaseTheme t; + return 0; +} \ No newline at end of file diff --git a/src/drawing.cpp b/src/drawing.cpp index 1e39ecd..6502128 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" @@ -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) { @@ -69,7 +76,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 +86,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); } } @@ -310,7 +317,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 +364,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 +377,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 +401,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,30 +426,131 @@ namespace Drawing { canvas->drawPath(path, lc); } - 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) { + inline void + drawIns(SkCanvas *canvas, float y0, float start, float yScaling, float xOffset, + float yOffset, const SkPaint &faceColor, SkRect &rect, float pH, float overhang, float width) { + 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); - 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); + float box_left = x - (width * 0.5); + + 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 + + } + + 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; } + 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, 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; @@ -472,6 +575,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; @@ -481,7 +585,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 +600,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 +609,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 +652,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; @@ -602,30 +691,29 @@ 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, - size_t idx, size_t nBlocks, int regionLen, + float pointSlop, float pH, float yScaledOffset, float xOffset, float regionPixels, + 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); } } 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) { @@ -633,7 +721,6 @@ namespace Drawing { int lastEnd = lastEndi - regionBegin; starti -= regionBegin; - lastEnd = (lastEnd < 0) ? 0 : lastEnd; int size = starti - lastEnd; if (size <= 0) { return; @@ -678,19 +765,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, SkPaint& fc5mc, SkPaint& fc5hmc, SkPaint& fcOther) { + 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(); + + float top = yScaledOffset + (pH / 3); + float middle = yScaledOffset + pH - (pH / 2); + 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; + 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 + fc5mc.setAlpha(mod_it->quals[j]); + canvas->drawPoint(x, (n_mods == 1) ? middle : top, fc5mc); + break; + case 'h': // 5hmC + fc5mc.setAlpha(mod_it->quals[j]); + canvas->drawPoint(x, (n_mods == 1) ? middle : bottom, fc5hmc); + break; + default: + fcOther.setAlpha(mod_it->quals[j]); + canvas->drawPoint(x, middle, fcOther); + 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; SkRect rect; SkPath path; - const Themes::BaseTheme &theme = opts.theme; - std::vector text_ins, text_del; + static std::vector text_ins; + static std::vector text_del; + text_ins.clear(); + text_del.clear(); int regionBegin = cl.region->start; int regionEnd = cl.region->end; @@ -699,7 +839,12 @@ namespace Drawing { float xScaling = cl.xScaling; float xOffset = cl.xOffset; float yOffset = cl.yOffset; - float regionPixels = cl.regionPixels; //regionLen * xScaling; + 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; bool plotPointedPolygons = cl.plotPointedPolygons; // regionLen < 50000; @@ -709,18 +854,22 @@ namespace Drawing { cl.skipDrawingReads = true; - float pH_05 = pH * 0.05; - float pH_95 = pH * 0.95; + 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) { 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); @@ -730,7 +879,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); @@ -739,46 +888,61 @@ 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, 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, 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, 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, nBlocks, regionLen, + a, canvas, path, rect, faceColor, edgeColor); } // add soft-clip blocks @@ -799,12 +963,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); } } @@ -828,50 +992,24 @@ 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); } } } } - // 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, pH_05, pH_95); - 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); - } - } 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); - } - } - } - } // add mismatches - if (regionLen > opts.snp_threshold && plotSoftClipAsBlock) { - continue; - } +// if (regionLen > opts.snp_threshold && plotSoftClipAsBlock) { +// continue; +// } if (l_seq == 0) { continue; } @@ -880,6 +1018,8 @@ namespace Drawing { mmPosOffset = 0.05; mmScaling = 0.9; } else { + + mmPosOffset = 0; mmScaling = 1; } @@ -893,6 +1033,39 @@ 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, fc5mc, fc5hmc, fcOther); + } + + // 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 + // float yScaledOffset = (Y * yScaling) + yOffset; + text_ins.emplace_back() = {SkTextBlob::MakeFromString(indelChars, fonts.overlay), + (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, 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); + } + } + } + } // add soft-clips if (!plotSoftClipAsBlock) { @@ -904,15 +1077,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; } } @@ -922,31 +1091,31 @@ 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; } } } } - // draw text last + // draw text deletions + insertions 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); + rect.setXYWH(t.x - monitorScale, t.box_y, t.box_w, pH); // middle + canvas->drawRect(rect, theme.fcIns); + 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); } -// canvas->restore(); - // draw connecting lines between linked alignments if (linkOp > 0) { if (!cl.linked.empty()) { @@ -964,12 +1133,12 @@ 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->blocks.empty() || + segB->blocks.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->blocks.front().end, segB->blocks.front().end); + 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; @@ -1170,8 +1339,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(); @@ -1180,7 +1348,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()) { @@ -1324,16 +1491,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()) { @@ -1477,7 +1648,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; @@ -1498,21 +1669,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; @@ -1589,7 +1756,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, @@ -1606,7 +1772,6 @@ namespace Drawing { std::vector &features = rgn.featuresInView[trackIdx]; features.clear(); - if (isGFF) { HGW::collectGFFTrackData(trk, features); } else { @@ -1643,7 +1808,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; @@ -1653,6 +1818,7 @@ namespace Drawing { canvas->drawTextBlob(t.text, t.x, t.y, opts.theme.tcDel); } } + canvas->restore(); } padX += stepX; @@ -1661,59 +1827,84 @@ 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); - - 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); - } + SkPaint paint, light_paint, line; + 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); -// 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 gap = 50; - float gap2 = 2 * gap; - float drawWidth = colWidth - gap2; + + 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; + + 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; } - 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) { + + 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; } - 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_two_thirds ); + path.lineTo(xp + drawWidth, top + yh_two_thirds); 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 / (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, + wb, + yh_two_thirds); + canvas->drawRect(rect, b.paint); + if (wb > 2) { + 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 333cebc..798c9b5 100644 --- a/src/drawing.h +++ b/src/drawing.h @@ -19,10 +19,9 @@ #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" #include "segments.h" #include "themes.h" @@ -34,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); @@ -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/glfw_keys.cpp b/src/glfw_keys.cpp index 3099fb0..f8aa5ad 100644 --- a/src/glfw_keys.cpp +++ b/src/glfw_keys.cpp @@ -5,11 +5,11 @@ #include #include #include -#include "../include/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 84fae4e..e723994 100644 --- a/src/glfw_keys.h +++ b/src/glfw_keys.h @@ -6,10 +6,10 @@ #include #include #include -#include "../include/unordered_dense.h" +#include namespace Keys { - void getKeyTable(ankerl::unordered_dense::map& kt); + void getKeyTable(std::unordered_map& kt); } diff --git a/src/hts_funcs.cpp b/src/hts_funcs.cpp index 653920a..02af9cf 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" @@ -159,7 +159,9 @@ 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 int parse_mods_threshold) { + bam1_t *src; hts_itr_t *iter_q; int tid = sam_hdr_name2tid(hdr_ptr, region->chrom.c_str()); @@ -174,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) { @@ -190,12 +193,12 @@ namespace HGW { readQueue.pop_back(); } + Segs::init_parallel(readQueue, threads, pool, parse_mods_threshold); + 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) { @@ -347,7 +350,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; @@ -366,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; @@ -379,7 +386,7 @@ namespace HGW { if (j < BATCH) { continue; } - Segs::init_parallel(readQueue, threads, pool); + Segs::init_parallel(readQueue, threads, pool, parse_mods_threshold); if (filter) { applyFilters_noDelete(filters, readQueue, hdr_ptr, col.bamIdx, col.regionIdx); } @@ -392,7 +399,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]); @@ -403,7 +410,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, parse_mods_threshold); if (!filters.empty()) { applyFilters_noDelete(filters, readQueue, hdr_ptr, col.bamIdx, col.regionIdx); } @@ -416,7 +423,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]); } @@ -429,10 +436,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) { -// if (region->end == 0) { -// return; -// } + float pointSlop, float textDrop, float pH, float monitorScale) { bam1_t *src; hts_itr_t *iter_q; @@ -448,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()); + 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) { @@ -469,7 +476,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()); } } @@ -547,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; @@ -597,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())); @@ -646,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())); @@ -671,7 +685,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, 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); @@ -1193,6 +1207,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; @@ -1535,7 +1550,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; @@ -1915,7 +1929,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) { @@ -1948,9 +1962,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); @@ -1973,9 +1988,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; @@ -2023,7 +2039,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 @@ -2050,26 +2066,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)) { @@ -2187,7 +2206,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 9e270ff..8daf2d7 100644 --- a/src/hts_funcs.h +++ b/src/hts_funcs.h @@ -15,11 +15,12 @@ #include "htslib/tbx.h" #include "parser.h" -#include "../include/IITree.h" -#include "../include/unordered_dense.h" -#include "../lib/libBigWig/bigWig.h" -//#include "../include/strnatcmp.h" -#include "../include/glob_cpp.hpp" +#include "IITree.h" +#include "ankerl_unordered_dense.h" +#include "bigWig.h" +//#include "../lib/libBigWig/bigWig.h" + +#include "glob_cpp.hpp" #include "segments.h" #include "themes.h" @@ -40,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); @@ -89,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 int parse_mods); void iterDrawParallel(Segs::ReadCollection &col, htsFile *b, @@ -108,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); @@ -130,6 +134,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: @@ -205,11 +210,11 @@ namespace HGW { enum TrackType { VCF, GW_TRACK, - IMAGES + IMAGES, }; 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 9557a7f..1798cbc 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" @@ -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; @@ -73,13 +76,15 @@ int main(int argc, char *argv[]) { if (!success) { } + 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" }; 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() @@ -102,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 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"); @@ -145,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)"); @@ -175,6 +186,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("--mods") + .default_value(false).implicit_value(true) + .help("Base modifications are shown"); program.add_argument("--low-mem") .default_value(false).implicit_value(true) .help("Reduce memory usage by discarding quality values"); @@ -208,6 +222,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); @@ -222,7 +238,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"); @@ -231,7 +250,11 @@ int main(int argc, char *argv[]) { } } - std::vector bam_paths; + 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"); @@ -242,16 +265,15 @@ 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]; - } 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; - 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 = "*"; @@ -261,7 +283,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"; @@ -294,32 +316,53 @@ 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::filesystem::exists(iopts.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::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; + } + + 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()) { + have_session_file = std::filesystem::exists(iopts.session_file); + if (have_session_file) { + use_session = true; + } 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){ @@ -348,7 +391,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); @@ -413,7 +456,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"; } } @@ -438,6 +481,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"); } @@ -456,9 +502,7 @@ 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"); } @@ -477,7 +521,15 @@ int main(int argc, char *argv[]) { } if (!iopts.no_show) { // plot something to screen - + if (use_session) { + 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 */ @@ -491,8 +543,11 @@ int main(int argc, char *argv[]) { plotter.addFilter(s); } + if (program.is_used("--ideogram")) { + plotter.addIdeogram(program.get("--ideogram")); + } - // 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); @@ -550,6 +605,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 @@ -628,6 +689,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(); @@ -669,7 +733,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 +741,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) { @@ -697,9 +761,9 @@ int main(int argc, char *argv[]) { iopts.dimensions.y); SkCanvas *canvas = rasterSurface->getCanvas(); if (iopts.link_op == 0) { - plotter.runDrawNoBuffer(canvas); + plotter.runDrawNoBufferOnCanvas(canvas); } else { - plotter.runDraw(canvas); + plotter.runDraw(); } img = rasterSurface->makeImageSnapshot(); @@ -743,6 +807,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); } @@ -773,10 +840,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 +852,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"; @@ -854,6 +922,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); } @@ -901,18 +972,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..01532a4 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" @@ -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; @@ -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"; } @@ -305,7 +305,9 @@ 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 == "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"; } @@ -457,8 +459,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") { @@ -675,13 +676,13 @@ 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"}) { 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"}) { @@ -707,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); } @@ -721,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; } @@ -755,12 +757,13 @@ 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"; } 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); } @@ -885,8 +888,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") { @@ -895,6 +896,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 98bac93..bf661ce 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" @@ -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); @@ -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/parser.cpp b/src/parser.cpp index 84aeb29..5d57d85 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -7,8 +7,9 @@ #include #include #include -#include "../include/unordered_dense.h" -#include "../include/termcolor.h" +#include +#include "termcolor.h" +#include "term_out.h" #include "utils.h" #include "parser.h" #include "segments.h" @@ -19,7 +20,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 +137,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 +162,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 +172,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 +190,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 +200,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 +238,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 +262,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 +283,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 +304,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 +322,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 +334,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 +380,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 +390,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 +399,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); @@ -492,11 +492,11 @@ 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; - if (!targetIndexes.empty() && targetIndexes[bamIdx][regionIdx] == 0) { + if (bamIdx >= 0 && !targetIndexes.empty() && targetIndexes[bamIdx][regionIdx] == 0) { return true; } @@ -659,11 +659,11 @@ 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, ';')) { - 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); @@ -726,38 +726,38 @@ 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; } } - 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 +765,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 +778,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 +787,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 +819,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 +850,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 +868,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 +901,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 +915,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") { @@ -932,4 +931,96 @@ 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; + } + + 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; + if (parts.back() == "./") { + globstr = "./*"; + } else { + globstr = parts.back() + "*"; + } + 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; + } + std::vector path_str; + for (auto &item : glob_paths) { + 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(); + } 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(); + return; + } } diff --git a/src/parser.h b/src/parser.h index c1bb0d1..f89789a 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 { @@ -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); @@ -103,19 +104,24 @@ 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); + 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); + + std::string tilde_to_home(std::string fpath); + + void tryTabCompletion(std::string &inputText, std::ostream& out, int& charIndex); } diff --git a/src/plot_commands.cpp b/src/plot_commands.cpp new file mode 100644 index 0000000..ec82dfd --- /dev/null +++ b/src/plot_commands.cpp @@ -0,0 +1,1375 @@ +// +// Created by kez on 10/05/24. +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(_WIN32) + +#else + #include +#endif + +#include "parser.h" +#include "plot_manager.h" +#include "term_out.h" +#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 { + NONE = 0, + UNKNOWN, + SILENT, + + TOO_MANY_OPTIONS, + CHROM_NOT_IN_REFERENCE, + FEATURE_NOT_IN_TRACKS, + BAD_REGION, + OPTION_NOT_SUPPORTED, + OPTION_NOT_UNDERSTOOD, + INVALID_PATH, + EMPTY_TRACKS, + EMPTY_BAMS, + EMPTY_REGIONS, + EMPTY_VARIANTS, + + PARSE_VCF, + PARSE_INPUT, + + }; + + using Plot = Manager::GwPlot; + + Err noOp(Plot* p) { + p->redraw = false; + p->processed = true; + return Err::NONE; + } + + Err triggerClose(Plot* p) { + p->triggerClose = true; + p->redraw = false; + p->processed = true; + return Err::NONE; + } + + 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], out); + } + p->redraw = false; + p->processed = true; + return Err::NONE; + } + + Err refreshGw(Plot* p) { + 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; cl.skipDrawingCoverage = false; cl.skipDrawingReads = false;} + return Err::NONE; + } + + Err line(Plot* p) { + p->drawLine = !p->drawLine; + p->redraw = true; + p->processed = true; + return Err::NONE; + } + + Err settings(Plot* p) { + p->last_mode = p->mode; + p->mode = Manager::Show::SETTINGS; + p->redraw = true; + p->processed = true; + return Err::NONE; + } + + Err sam(Plot* p, std::string& command, std::vector& parts, std::ostream& out) { + if (!p->selectedAlign.empty()) { + 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 = Parse::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 Err::NONE; + } + + 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; + p->processed = false; + p->redraw = true; + p->imageCache.clear(); + p->imageCacheQueue.clear(); + return Err::NONE; + } + + Err mismatches(Plot* p) { + p->opts.snp_threshold = (p->opts.snp_threshold == 0) ? std::stoi(p->opts.myIni["view_thresholds"]["snp"]) : 0; + p->processed = false; + p->redraw = true; + p->imageCache.clear(); + p->imageCacheQueue.clear(); + return Err::NONE; + } + + Err mods(Plot* p) { + p->opts.parse_mods = !(p->opts.parse_mods); + p->processed = false; + p->redraw = true; + p->imageCache.clear(); + p->imageCacheQueue.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; + p->processed = false; + p->redraw = true; + p->imageCache.clear(); + p->imageCacheQueue.clear(); + return Err::NONE; + } + + 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; + } else { + p->processed = false; + } + p->redraw = true; + p->imageCache.clear(); + p->imageCacheQueue.clear(); + 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(); + p->imageCacheQueue.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(); + p->imageCacheQueue.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; + } + + 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; + p->opts.link_op = 2; + p->opts.link = "all"; + } else if (parts.size() == 2) { + if (parts[1] == "sv") { + 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; + p->opts.link_op = 0; + p->opts.link = "none"; + } + } + if (relink) { + p->imageCache.clear(); + p->imageCacheQueue.clear(); + HGW::refreshLinked(p->collections, p->opts, &p->samMaxY); + p->redraw = true; + p->processed = true; + } + 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, out); + } catch (...) { + 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) { + 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; + } + + 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(), out); + p->redraw = false; + p->processed = true; + return Err::NONE; + } + + Err addFilter(Plot* p, std::string& command, std::ostream& out) { + std::string str = command; + str.erase(0, 7); + if (str.empty()) { + return Err::NONE; + } + for (auto &s: Utils::split(str, ';')) { + 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); + out << command << std::endl; + } + } + p->imageCache.clear(); + p->imageCacheQueue.clear(); + p->redraw = true; + p->processed = false; + return Err::NONE; + } + + 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(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()); + 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()); + out << termcolor::green << t << termcolor::reset << rest << "\t"; + } + } + } + } + i += 1; + } + out << std::endl; + } + } + p->redraw = false; + p->processed = true; + return Err::NONE; + } + + 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(); + 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()); + p->processed = false; + p->processBam(); + p->highlightQname(); + p->redraw = true; + p->processed = true; + p->imageCache.clear(); + p->imageCacheQueue.clear(); + } + } + return Err::NONE; + } + + Err findRead(Plot* p, std::vector parts, std::ostream& out) { + if (!p->target_qname.empty() && parts.size() == 1) { + return Err::NONE; + } else if (parts.size() == 2) { + p->target_qname = parts.back(); + } else { + 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(); + p->imageCacheQueue.clear(); + return Err::NONE; + } + + Err setYlim(Plot* p, std::vector parts, std::ostream& out) { + try { + if (!p->opts.tlen_yscale) { + p->opts.ylim = std::stoi(parts.back()); + p->samMaxY = p->opts.ylim; + } else { + 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"; + return Err::NONE; + } + p->imageCache.clear(); + p->imageCacheQueue.clear(); + HGW::refreshLinked(p->collections, p->opts, &p->samMaxY); + p->processed = true; + p->redraw = true; + return Err::NONE; + } + + Err indelLength(Plot* p, std::vector parts, std::ostream& out) { + int indel_length; + try { + indel_length = std::stoi(parts.back()); + } catch (...) { + 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) { + p->processed = true; + } else { + p->processed = false; + } + p->redraw = true; + p->imageCache.clear(); + p->imageCacheQueue.clear(); + return Err::NONE; + } + + 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 (...) { + out << termcolor::red << "Error:" << termcolor::reset << " bam index not understood\n"; + return Err::NONE; + } + p->removeBam(ind); + } else if (Utils::startsWith(parts.back(), "track")) { + parts.back().erase(0, 5); + try { + ind = std::stoi(parts.back()); + } catch (...) { + 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 (...) { + out << termcolor::red << "Error:" << termcolor::reset << " region index not understood\n"; + return Err::NONE; + } + 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(); + } + p->imageCacheQueue.clear(); + p->imageCacheQueue.clear(); + return Err::NONE; + } + + Err cov(Plot* p, std::vector parts, std::ostream& out) { + if (parts.size() > 2) { + 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; + } else { + p->opts.max_coverage = 0; + } + } else { + try { + p->opts.max_coverage = std::stoi(parts.back()); + } catch (...) { + out << termcolor::red << "Error:" << termcolor::reset << " 'cov NUMBER' not understood\n"; + return Err::NONE; + } + } + 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(); + p->imageCacheQueue.clear(); + return Err::NONE; + } + + 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->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->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->imageCacheQueue.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(); + p->imageCacheQueue.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 { + dummy_region.chromLength = faidx_seq_len(p->fai, dummy_region.chrom.c_str()); + 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; + for (auto &cl: p->collections) { cl.skipDrawingCoverage = false; cl.skipDrawingReads = false;} + p->imageCache.clear(); + p->imageCacheQueue.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(), out); + } 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 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; + int res = 0; + std::string full_path = Parse::tilde_to_home(o_str); + const char* outf = full_path.c_str(); + 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 { + 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; + 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) { + 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)); + } + } + 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]); + } + + 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.align.delegate->core.tid == b.align.delegate->core.tid) { + return a.align.delegate->core.pos > b.align.delegate->core.pos; + } + return false; + }; + // 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, 0); // no need to parse mods here + 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, p->opts.parse_mods); + pq.push(item); + } else { + bam_destroy1(item.align.delegate); + } + pq.pop(); + count += 1; + } + // clean up + for (size_t i=0; i < region_iters.size(); ++i) { + bam_itr_destroy(region_iters[i]); + } + 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; + } + + 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) { + out << "Saved alignments: " << parts.back() << std::endl; + 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] == ">>") { + return Err::OPTION_NOT_SUPPORTED; + } else { + return Err::OPTION_NOT_UNDERSTOOD; + } + } else { + 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; + } + + 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; + } + + 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) { + return Err::OPTION_NOT_UNDERSTOOD; + } + 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()); + 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; + rgn.markerPosEnd = p->regions[p->regionSelection].markerPosEnd; + } + 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()) { + 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(); + p->imageCacheQueue.clear(); + } + 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 == "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; } + 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 if (c == "fc5mc") { e = Themes::GwPaint::fc5mc; } + else if (c == "fc5hmc") { e = Themes::GwPaint::fc5hmc; } + 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) { + case NONE: + applied->push_back(command); + 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_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; + 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 + 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 = { + + {":", 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); }}, + {"ins", PARAMS { return insertions(p); }}, + {"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); }}, + {"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); }}, + {"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); }}, + {"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); }}, + {"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); }}, + + }; + + auto it = functionMap.find(parts[0]); + Err res; + if (it != functionMap.end()) { + res = it->second(p, command, parts, out); // Execute the mapped function + } else { + res = infer_region_or_feature(p, command, parts); + } + 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_commands.h b/src/plot_commands.h new file mode 100644 index 0000000..6d44da4 --- /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 { + + 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 32780a1..dd0bf70 100644 --- a/src/plot_controls.cpp +++ b/src/plot_controls.cpp @@ -17,9 +17,10 @@ #include "hts_funcs.h" #include "parser.h" #include "plot_manager.h" +#include "plot_commands.h" #include "menu.h" #include "segments.h" -#include "../include/termcolor.h" +#include "termcolor.h" #include "term_out.h" #include "themes.h" @@ -32,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; @@ -92,7 +69,8 @@ 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) { - if (key == GLFW_KEY_LEFT_CONTROL || key == GLFW_KEY_LEFT_SUPER) { + std::ostream& out = (terminalOutput) ? std::cout : outStr; + 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) { @@ -109,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; @@ -118,6 +100,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; @@ -155,7 +141,7 @@ namespace Manager { if (mode == SETTINGS) { return key; } - std::cout << std::endl; + out << std::endl; if (!commandHistory.empty()) { inputText = commandHistory.back(); } @@ -167,7 +153,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(); @@ -187,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 ) { @@ -265,15 +252,16 @@ 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; - std::cout << "\n"; + out << "\n"; return key; } else if (key == GLFW_KEY_TAB) { if (mode != SETTINGS) { @@ -295,7 +283,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())) { @@ -305,8 +293,9 @@ namespace Manager { redraw = true; processed = true; imageCache.clear(); + imageCacheQueue.clear(); commandToolTipIndex = -1; - std::cout << "\n"; + out << "\n"; return GLFW_KEY_ENTER; } inputText += " "; @@ -322,13 +311,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; } } @@ -376,11 +365,11 @@ namespace Manager { 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 @@ -391,6 +380,12 @@ 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 { @@ -411,12 +406,76 @@ namespace Manager { } } if (key == GLFW_KEY_ENTER) { - std::cout << std::endl; + out << std::endl; } return key; } - void GwPlot::highlightQname() { // todo make this more efficient + void GwPlot::removeBam(int index) { + if (index >= (int) bams.size()) { + 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) { + 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(); + imageCacheQueue.clear(); + } + + void GwPlot::removeTrack(int index) { + if (index >= (int)tracks.size()) { + 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) { + 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(); + imageCacheQueue.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::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) { + return col.regionIdx == index; + }), collections.end()); + processed = false; + redraw = true; + inputText = ""; + imageCache.clear(); + imageCacheQueue.clear(); + } + + void GwPlot::highlightQname() { for (auto &cl : collections) { for (auto &a: cl.readQueue) { if (bam_get_qname(a.delegate) == target_qname) { @@ -429,8 +488,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; @@ -438,11 +496,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(); @@ -456,854 +509,23 @@ namespace Manager { return false; } } - processText = false; // all command text will be processed below - - if (inputText == "q" || inputText == "quit") { - triggerClose = true; - redraw = false; - processed = true; - return false; -// throw CloseException(); - } 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")) { - 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; - } - 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(); - 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; - } - 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(); - 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; - } - 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(); - 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") { - 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 (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); - 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; -// 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) { - 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; + glfwSetCursor(window, normalCursor); + 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; @@ -1315,14 +537,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(); @@ -1331,14 +553,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; } @@ -1346,44 +568,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; } @@ -1392,6 +615,7 @@ namespace Manager { redraw = true; processed = false; imageCache.clear(); + imageCacheQueue.clear(); inputText = ""; opts.editing_underway = false; textFromSettings = false; @@ -1399,6 +623,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) { @@ -1407,9 +632,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); } @@ -1417,7 +642,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) { @@ -1450,11 +675,12 @@ 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 // processed using registerKey + std::ostream& out = (terminalOutput) ? std::cout : outStr; key = registerKey(window, key, scancode, action, mods); if (key == GLFW_KEY_UNKNOWN || captureText) { return; @@ -1465,7 +691,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) { @@ -1503,8 +729,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) { @@ -1539,8 +765,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) { @@ -1576,8 +802,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) { @@ -1629,14 +855,15 @@ 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) { 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); + 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; @@ -1671,7 +898,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) { @@ -1680,7 +907,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) { @@ -1775,54 +1002,56 @@ 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(); + imageCacheQueue.clear(); HGW::refreshLinked(collections, opts, &samMaxY); redraw = true; } } - void GwPlot::pathDrop(GLFWwindow* wind, int count, const char** paths) { + void GwPlot::addTrack(std::string &path, bool print_message=true) { + std::ostream& out = (terminalOutput) ? std::cout : outStr; 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; - 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(); + if (Utils::endsWith(path, ".bam") || Utils::endsWith(path, ".cram")) { + good = true; + if (print_message) { + out << 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) { + out << termcolor::magenta << "\nFile " << termcolor::reset << variantTracks[variantFileSelection].path << "\n"; + } + } else { + tracks.push_back(HGW::GwTrack()); + try { + tracks.back().open(path, true); + tracks.back().variant_distance = &opts.variant_distance; + if (print_message) { + out << termcolor::magenta << "\nTrack " << termcolor::reset << path << "\n"; } + } catch (...) { + tracks.pop_back(); } - ++paths; } if (good) { processed = false; @@ -1830,6 +1059,18 @@ namespace Manager { } } + void GwPlot::pathDrop(int count, const char** paths) { + for (int i=0; i < count; ++ i) { + 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) { if (y <= refSpace) { return REFERENCE_TRACK; //-2 @@ -1894,7 +1135,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; @@ -1916,7 +1157,9 @@ namespace Manager { } } - void GwPlot::mouseButton(GLFWwindow* wind, int button, int action, int mods) { + 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); @@ -1971,7 +1214,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; @@ -2006,13 +1249,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; } } @@ -2037,7 +1280,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; @@ -2068,8 +1311,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); @@ -2078,14 +1321,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) - std::cout << std::endl; + out << std::endl; return; } + level = (int)level; if (cl.vScroll < 0) { level += cl.vScroll + 1; } @@ -2094,34 +1342,60 @@ 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, [&](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) { - 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); + 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 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, opts.indel_length, opts.parse_mods); + } redraw = true; processed = true; cl.skipDrawingReads = false; - cl.skipDrawingCoverage = true; + cl.skipDrawingCoverage = false; break; } } 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); + 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, pos, opts.indel_length, opts.parse_mods); + } redraw = true; processed = true; cl.skipDrawingReads = false; - cl.skipDrawingCoverage = true; + cl.skipDrawingCoverage = false; } } + if (bnd == cl.readQueue.begin()) { + break; + } --bnd; } xDrag = DRAG_UNSET; @@ -2192,7 +1466,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]; @@ -2356,6 +1630,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(); @@ -2363,19 +1638,21 @@ 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(GLFWwindow* wind, double xPos, double yPos) { + void GwPlot::mousePos(double xPos, double yPos) { + std::ostream& out = (terminalOutput) ? std::cout : outStr; + GLFWwindow* wind = window; int windX, windY; glfwGetWindowSize(wind, &windX, &windY); if (yPos < 0 || xPos < 0 || xPos > windX || yPos > windY) { @@ -2391,8 +1668,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); @@ -2470,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; } } @@ -2484,9 +1766,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; @@ -2582,7 +1866,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 @@ -2590,13 +1874,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; } } @@ -2613,8 +1897,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); @@ -2636,14 +1920,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); @@ -2651,34 +1935,40 @@ 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_left, 0, GLFW_PRESS, 0); } else { - keyPress(wind, opts.scroll_left, 0, GLFW_PRESS, 0); + 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(wind, opts.scroll_right, 0, GLFW_PRESS, 0); + keyPress(opts.scroll_left, 0, GLFW_PRESS, 0); } else { - keyPress(wind, opts.scroll_left, 0, GLFW_PRESS, 0); + keyPress(opts.scroll_right, 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); 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 e63434f..fc48512 100644 --- a/src/plot_manager.cpp +++ b/src/plot_manager.cpp @@ -30,14 +30,15 @@ #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 "term_out.h" #include "themes.h" -#include "../include/window_icon.h" +#include "window_icon.h" using namespace std::literals; @@ -48,22 +49,22 @@ namespace Manager { void HiddenWindow::init(int width, int height) { if (!glfwInit()) { - std::cerr<<"ERROR: could not initialize GLFW3"< &bampaths, Themes::IniOptions &opt, std::vector ®ions, + 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; @@ -78,13 +79,16 @@ namespace Manager { captureText = false; drawToBackWindow = false; 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()) { fai = fai_load(reference.c_str()); if (fai == nullptr) { - std::cerr << termcolor::red << "Error:" << termcolor::reset << " reference genome could not be opened " << reference << std::endl; + std::cerr << "Error: reference genome could not be opened " << reference << std::endl; std::exit(-1); } } @@ -104,9 +108,11 @@ namespace Manager { tracks.reserve(track_paths.size() + track_paths_temp.size()); for (const 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; + if (terminalOutput) { + 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; @@ -116,7 +122,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; } @@ -161,15 +167,28 @@ 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); + rasterCanvas = rasterSurface->getCanvas(); + rasterSurfacePtr = &rasterSurface; + return pixelMemory.size(); + } + void GwPlot::init(int width, int height) { glfwSetErrorCallback(ErrorCallback); if (!glfwInit()) { - std::cerr<<"ERROR: could not initialize GLFW3"<(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); if (!window) { - std::cerr<<"ERROR: could not create window with GLFW3"<> 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) @@ -358,12 +387,15 @@ namespace Manager { f.close(); } + void GwPlot::addIdeogram(std::string path) { + ideogram_path = path; + Themes::readIdeogramFile(path, ideogram, opts.theme); + } + 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"); } } @@ -403,12 +435,126 @@ 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); + 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"]; + 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; + } + 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; + } + + 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"]) { + 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, 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; + } + } + + 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(); + } + } + 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); + windowResize(opts.dimensions.x, opts.dimensions.y); + redraw = true; + } + + void GwPlot::saveSession(std::string output_session="") { + std::vector track_paths; + for (const auto& item: tracks) { + if (item.kind != HGW::FType::ROI) { + track_paths.push_back(item.path); + } + } + std::vector> variant_paths_info; + for (const auto& item: variantTracks) { + variant_paths_info.push_back({item.path, item.blockStart}); + } + int xpos, ypos; + glfwGetWindowPos(window, &xpos, &ypos); + int windX, windY; + glfwGetWindowSize(window, &windX, &windY); + opts.saveCurrentSession(reference, ideogram_path, bam_paths, track_paths, regions, variant_paths_info, commandsApplied, + output_session, mode, xpos, ypos, monitorScale, windX, windY); + } + int GwPlot::startUI(GrDirectContext* sContext, SkSurface *sSurface, int delay) { - std::cerr << "Type ':help' or ':h' for more info\n"; + if (!opts.session_file.empty() && reference.empty()) { + std::cout << "Loading session: " << opts.session_file << std::endl; + loadSession(); + } + if (terminalOutput) { + std::cerr << "\nType" << termcolor::green << " '/help'" << termcolor::reset << " for more info\n"; + } else { + outStr << "Type '/help' for more info\n"; + } vCursor = glfwCreateStandardCursor(GLFW_VRESIZE_CURSOR); setGlfwFrameBufferSize(); -// rasterSurface = SkSurface::MakeRasterN32Premul(opts.dimensions.x, opts.dimensions.y); fetchRefSeqs(); opts.theme.setAlphas(); @@ -418,8 +564,11 @@ namespace Manager { } else { mouseOverTileIndex = 0; bboxes = Utils::imageBoundingBoxes(opts.number, (float)fb_width, (float)fb_height); - std::cout << termcolor::magenta << "File " << termcolor::reset << variantTracks[variantFileSelection].path << "\n"; - + if (terminalOutput) { + std::cout << termcolor::magenta << "File " << termcolor::reset << variantTracks[variantFileSelection].path << "\n"; + } else { + outStr << "File " << variantTracks[variantFileSelection].path << "\n"; + } } bool wasResized = false; @@ -432,20 +581,22 @@ namespace Manager { if (delay > 0) { std::this_thread::sleep_for(std::chrono::milliseconds(delay)); } + 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(); } } - drawOverlay(sSurface->getCanvas(), sContext, sSurface); + drawOverlay(sSurface->getCanvas()); + 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(); @@ -456,8 +607,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(); @@ -472,7 +621,7 @@ namespace Manager { if (!backendRenderTarget.isValid()) { std::cerr << "ERROR: backendRenderTarget was invalid" << std::endl; glfwTerminate(); - std::terminate(); + std::exit(-1); } sSurface = SkSurface::MakeFromBackendRenderTarget(sContext, @@ -483,15 +632,12 @@ namespace Manager { nullptr).release(); if (!sSurface) { std::cerr << "ERROR: sSurface could not be initialized (nullptr). The frame buffer format needs changing\n"; - std::terminate(); + std::exit(-1); } - 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); + rasterSurface = SkSurface::MakeRasterN32Premul(fb_width,fb_height); + rasterCanvas = rasterSurface->getCanvas(); + rasterSurfacePtr = &rasterSurface; resizeTimer = std::chrono::high_resolution_clock::now(); @@ -503,9 +649,12 @@ namespace Manager { } saveLabels(); + 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 - std::cerr << "\nGw finished\n"; + // 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"; + } exit(EXIT_SUCCESS); } @@ -523,30 +672,25 @@ 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()) { - 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) { @@ -572,8 +716,8 @@ namespace Manager { collections[idx].mmVector.clear(); } } - 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); + if (reg->end - reg->start < opts.low_memory || opts.link_op != 0) { + HGW::collectReadsAndCoverage(collections[idx], b, hdr_ptr, index, opts.threads, reg, (bool)opts.max_coverage, filters, pool, parse_mods_threshold); int maxY = Segs::findY(collections[idx], collections[idx].readQueue, opts.link_op, opts, reg, false); if (maxY > samMaxY) { samMaxY = maxY; @@ -647,12 +791,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; @@ -665,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; @@ -677,7 +820,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; @@ -687,13 +835,13 @@ 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) { // 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); @@ -707,59 +855,67 @@ namespace Manager { processBam(); setScaling(); - for (auto &cl: collections) { + 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); - 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() && opts.link_op == 0) { // low memory mode will be used + cl.clear(); + if (opts.threads == 1) { + HGW::iterDraw(cl, bams[cl.bamIdx], headers[cl.bamIdx], indexes[cl.bamIdx], + ®ions[cl.regionIdx], (bool) opts.max_coverage, + filters, opts, 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); + 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(); - } + if (opts.max_coverage) { Drawing::drawCoverage(opts, collections, canvasR, fonts, covY, refSpace); } 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(); @@ -769,41 +925,70 @@ 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()); - + imageCacheQueue.emplace_back(frameId, rasterSurfacePtr[0]->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::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 = 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); + 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, GrDirectContext *sContext, SkSurface *sSurface) { + void GwPlot::drawOverlay(SkCanvas *canvas) { + if (!imageCacheQueue.empty()) { while (imageCacheQueue.front().first != frameId) { imageCacheQueue.pop_front(); } 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) { @@ -814,7 +999,9 @@ 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); + + // 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(); @@ -832,6 +1019,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{}; @@ -864,51 +1052,8 @@ 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); - } + // 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()); @@ -929,45 +1074,77 @@ namespace Manager { } } + // command box if (captureText && !opts.editing_underway) { fonts.setFontSize(yScaling, monitorScale); SkRect rect{}; float height_f = fonts.overlayHeight * 2; float x = 50; float w = fb_width - 100; + + SkPaint bg = opts.theme.bgMenu; + 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->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); + canvas->drawTextBlob(blob, x + 14 + fonts.overlayWidth + fonts.overlayWidth, yy + (fonts.overlayHeight * 1.3) + pad + pad, grey); } + //yy += pad; - 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->drawRoundRect(rect, 10, 10, bg); + } + + for (const auto &cmd : Menu::commandToolTip) { std::string cmd_s = cmd; if (!inputText.empty() && !Utils::startsWith(cmd_s, inputText)) { @@ -976,8 +1153,8 @@ namespace Manager { if (cmd_s == inputText) { 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); +// 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); tip_paint.setStyle(SkPaint::kStrokeAndFill_Style); @@ -1001,8 +1178,55 @@ 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) { + 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) { + 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); @@ -1015,7 +1239,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); @@ -1026,8 +1250,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) { @@ -1037,13 +1259,13 @@ namespace Manager { int endIdx = bStart + bLen; currentVarTrack->iterateToIndex(endIdx); for (int i=bStart; imultiRegions.size() && !bams.empty()) { regions = currentVarTrack->multiRegions[i]; - runDraw(canvas); + runDrawOnCanvas(canvas); + sContext->flush(); sk_sp img(sSurface->makeImageSnapshot()); imageCache[i] = img; - sContext->flush(); } } } @@ -1058,7 +1280,7 @@ namespace Manager { [&](const int a, const int b) { for (int i=a; i data(nullptr); @@ -1101,7 +1323,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 { @@ -1128,7 +1349,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; @@ -1162,13 +1383,13 @@ namespace Manager { redraw = false; } - void GwPlot::runDraw(SkCanvas *canvas) { + void GwPlot::runDrawOnCanvas(SkCanvas *canvas) { fetchRefSeqs(); processBam(); 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); @@ -1178,14 +1399,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(rasterCanvas); + } + + void GwPlot::runDrawNoBufferOnCanvas(SkCanvas* canvas) { // 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 = rasterCanvas; + canvas->drawPaint(opts.theme.bgPaint); + fetchRefSeqs(); // This is a subset of processBam function: @@ -1196,6 +1423,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(); @@ -1216,8 +1444,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]; @@ -1227,10 +1454,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; } @@ -1242,11 +1469,32 @@ 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; } + void GwPlot::runDrawNoBuffer() { + runDrawNoBufferOnCanvas(rasterCanvas); + } + + + sk_sp GwPlot::makeImage() { + makeRasterSurface(); + runDraw(); + sk_sp img(rasterSurfacePtr[0]->makeImageSnapshot()); + return img; + } + + void GwPlot::rasterToPng(const char* path) { + sk_sp img(rasterSurfacePtr[0]->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()); @@ -1269,12 +1517,12 @@ namespace Manager { FILE* fout = stdout; #if defined(_WIN32) || defined(_WIN64) HANDLE h = (HANDLE) _get_osfhandle(_fileno(fout)); - DWORD t = GetFileType(h); - if (t == FILE_TYPE_CHAR) { + DWORD t = GetFileType(h); + if (t == FILE_TYPE_CHAR) { } // fine - if (t != FILE_TYPE_PIPE) { - std::cerr << "Error: attempting to write to a bad PIPE. This is unsupported on Windows" << std::endl; - std::terminate(); + if (t != FILE_TYPE_PIPE) { + std::cerr << "Error: attempting to write to a bad PIPE. This is unsupported on Windows" << std::endl; + std::exit(-1); } #endif fwrite(png->data(), 1, png->size(), fout); @@ -1290,13 +1538,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..9112841 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" @@ -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,20 +70,36 @@ 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); ~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; bool drawToBackWindow; + bool triggerClose; + bool redraw; + 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; + std::string ideogram_path; + + std::string inputText; + + std::string target_qname; + std::vector bam_paths; std::vector bams; std::vector headers; @@ -99,16 +116,20 @@ 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 + std::vector filters; - ankerl::unordered_dense::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; @@ -117,6 +138,8 @@ namespace Manager { GLFWwindow* backWindow; sk_sp rasterSurface; + sk_sp* rasterSurfacePtr; // option to use externally managed surface (faster) + SkCanvas* rasterCanvas; Show mode; Show last_mode; @@ -131,8 +154,24 @@ namespace Manager { void setRasterSize(int width, int height); + int makeRasterSurface(); + + void rasterToPng(const char* path); + + 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 addIdeogram(std::string path); + void addFilter(std::string &filter_str); void setOutLabelFile(const std::string &path); @@ -153,25 +192,29 @@ 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); + void loadSession(); 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(SkCanvas *canvas); + void runDraw(); - void runDrawNoBuffer(SkCanvas *canvas); + void runDrawOnCanvas(SkCanvas *canvas); + + void runDrawNoBuffer(); // draws to canvas managed by GwPlot (slower) + + void runDrawNoBufferOnCanvas(SkCanvas* canvas); // draws to external canvas (faster) sk_sp makeImage(); @@ -179,39 +222,38 @@ namespace Manager { int printRegionInfo(); + bool commandProcessed(); + + void highlightQname(); + + void saveSession(std::string out_session); private: long frameId; - bool redraw; - bool processed; - bool drawLine; bool resizeTriggered; bool regionSelectionTriggered; bool textFromSettings; - bool triggerClose; + std::chrono::high_resolution_clock::time_point resizeTimer, regionTimer; - std::string inputText; - std::string target_qname; std::string cursorGenomePos; int target_pos; bool captureText, shiftPress, ctrlPress, processText; bool tabBorderPress; - std::vector< std::string > commandHistory; + int commandIndex, charIndex; - int mouseOverTileIndex; 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 pixelMemory; + uint32_t minGapSize; // std::vector> extraPixelArrays; // one for each thread @@ -222,17 +264,17 @@ namespace Manager { int clickedIdx; int commandToolTipIndex; - HGW::GwVariantTrack *currentVarTrack; - std::vector bboxes; + std::unordered_map> ideogram; + BS::thread_pool pool; void drawScreen(SkCanvas* canvas, GrDirectContext* sContext, SkSurface *sSurface); 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); @@ -242,19 +284,16 @@ namespace Manager { int registerKey(GLFWwindow* window, int key, int scancode, int action, int mods); - bool commandProcessed(); - void updateSettings(); int getCollectionIdx(float x, float y); - 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); + void drawCursorPosOnRefSlider(SkCanvas *canvas); + }; void imageToPng(sk_sp &img, std::filesystem::path &outdir); diff --git a/src/segments.cpp b/src/segments.cpp index 16cd662..1b1423b 100644 --- a/src/segments.cpp +++ b/src/segments.cpp @@ -236,16 +236,451 @@ 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 + 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; - 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, @@ -253,14 +688,12 @@ namespace Segs { u, u, u, u, u, u, u, u, INV_F}; - void align_init(Align *self) { //noexcept { + void align_init(Align *self, const int parse_mods_threshold) { // auto start = std::chrono::high_resolution_clock::now(); + 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; @@ -273,11 +706,10 @@ 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); for (k = 0; k < cigar_l; k++) { op = cigar_p[k] & BAM_CIGAR_MASK; @@ -285,28 +717,27 @@ namespace Segs { switch (op) { case BAM_CMATCH: case BAM_CEQUAL: case BAM_CDIFF: - if (last_op == BAM_CINS) { - 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; break; - case BAM_CDEL: case BAM_CREF_SKIP: + case BAM_CREF_SKIP: + op = BAM_CDEL; pos += l; + seq_index += l; break; case BAM_CSOFT_CLIP: if (k == 0) { - self->cov_start -= (int)l; self->left_soft_clip = (int)l; + seq_index += l; } else { - self->cov_end += l; self->right_soft_clip = (int)l; } break; @@ -315,8 +746,10 @@ namespace Segs { default: // Match case --> MATCH, EQUAL, DIFF break; } - last_op = op; } + 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; uint32_t flag = src->core.flag; @@ -326,13 +759,43 @@ namespace Segs { self->has_SA = false; } + 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) { + hts_base_mod mods[10]; + 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; + size_t j=0; + for (size_t m=0; m < std::min((size_t)4, (size_t)nm); ++m) { + 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; + j += 1; + } + } + mi.n_mods = (uint8_t)j; + nm = bam_next_basemod(src, mod_state, mods, 10, &pos); + } + } + delete mod_state; + } + + self->y = -1; // -1 has no level, -2 means initialized but filtered if (flag & 1) { // paired-end 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,40 +806,38 @@ 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); } 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) { + 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); + align_init(&aln, parse_mods_threshold); } } else { pool.parallelize_loop(0, aligns.size(), - [&aligns](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]); + align_init(&aligns[i], parse_mods_threshold); }) .wait(); } } - ReadCollection::ReadCollection() { + EXPORT ReadCollection::ReadCollection() { vScroll = 0; collection_processed = false; skipDrawingReads = false; @@ -408,14 +869,12 @@ 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(); 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.block_ends[idx]; + uint32_t block_e = align.blocks[idx].end; 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; @@ -439,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) { @@ -523,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; @@ -539,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; @@ -560,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; @@ -576,6 +1043,47 @@ namespace Segs { return samMaxY; } + 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; } + 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 + }; + + // used for drawing mismatches over coverage track void findMismatches(const Themes::IniOptions &opts, ReadCollection &collection) { std::vector &mm_array = collection.mmVector; @@ -630,28 +1138,29 @@ 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) { 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; @@ -673,48 +1182,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 921d6da..d49f662 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 { + enum EXPORT Pattern { u = 0, NORMAL = 0, DEL = 1, @@ -32,9 +33,30 @@ namespace Segs { // typedef int64_t hts_pos_t; - struct InsItem { + 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]; + ModItem () { + index = -1; + mods[0] = 0; + mods[1] = 0; + mods[2] = 0; + mods[3] = 0; + } + }; + // // struct QueueItem { // uint32_t c_s_idx, l; @@ -54,26 +76,25 @@ 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 { + 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; - bool has_SA, initialized; - std::vector block_starts, block_ends; + bool has_SA; + std::vector blocks; std::vector any_ins; - Align(bam1_t *src) { - delegate = src; - initialized = false; - } + std::vector any_mods; + + Align(bam1_t *src) { delegate = src; } }; - struct Mismatches { + struct EXPORT 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; @@ -85,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; @@ -98,11 +119,11 @@ namespace Segs { void clear(); }; - void align_init(Align *self); // noexcept; + 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); + 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 a70a4f8..6fe3edd 100644 --- a/src/term_out.cpp +++ b/src/term_out.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #if !defined(__EMSCRIPTEN__) #include @@ -20,97 +21,134 @@ #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" 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 << "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"; - 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 << " 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"; - 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 << "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"; + 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 << "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"; + 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 << "roi region? name? " << termcolor::reset << "Add a region of interest\n"; + out << termcolor::green << "sam " << termcolor::reset << "Print selected read in sam format\n"; + out << termcolor::green << "save filename " << termcolor::reset << "Save reads (.bam/.cram), snapshot (.png) or session (.ini) to file\n"; + out << termcolor::green << "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"; -// } 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 << " 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 == "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" + " 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" + " 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" + " 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" + " 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") { - 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" @@ -121,47 +159,83 @@ 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") { - 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"; - } 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"; + 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") { - 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 == "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") { - 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") { + out << " Save reads, snapshot or session to file.\n" + " The filepath extension will determine the output file type.\n\n" + " Examples:\n" + " 'save reads.bam' # Save visible reads to reads.bam file.\n" + " 'save reads.bam [0, 1]' # Indexing can be used, here reads from row 0, column 1 will be saved\n" + " 'save reads.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.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" + " are incompatible.\n" + " If two regions overlap, then reads in both regions are only written once." + "\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" @@ -175,15 +249,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" @@ -195,17 +269,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) { @@ -218,7 +292,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; @@ -230,308 +304,486 @@ 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, 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) { - std::cout << "*"; + out << "*"; return; } - uint32_t l, cigar_l, op, k; + 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; 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; + bool done_final_op = 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) { - std::cout << "..."; - return; + 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) { - for (int n=0; n < (int)l; ++n) { - std::cout << "-"; + if (started || std::abs(pos - p) < max / 2 || std::abs(pos - block_end) < max / 2 || overlaps) { + started = true; + } else { + p += l; + continue; + } + printed += l; + 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; + if (printed > max) { + if (done_final_op) { + out << "..."; + break; + } else { + done_final_op = true; + } + } + } else if (op == BAM_CMATCH) { - for (int n = 0; n < (int)l; ++n) { + 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); bool mm = false; if (p >= refStart && p < refEnd && refSeq != nullptr && std::toupper(refSeq[p - refStart]) != basemap[base]) { mm = true; } - if (mm) { - std::cout << 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; + 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 { + switch (basemap[base]) { case 65 : - std::cout << termcolor::green << "A" << termcolor::reset; + out << "A"; break; case 67 : - std::cout << termcolor::blue << "C" << termcolor::reset; + out << "C"; break; case 71 : - std::cout << termcolor::yellow << "G" << termcolor::reset; + out << "G"; break; case 78 : - std::cout << termcolor::grey << "N" << termcolor::reset; + out << "N"; break; case 84 : - std::cout << termcolor::red << "T" << termcolor::reset; + out << "T"; break; } + } + } else { + if (mod_it != mod_end && i == mod_it->index) { + 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; + } + } + + ++mod_it; + } else { + out << "_"; + } + } + + i += 1; + if (p == pos) { + out << termcolor::reset ; + } + p += 1; + + } + if (printed > max*2) { + if (done_final_op) { + out << "..."; + break; } else { + done_final_op = true; + } + } + + } else if (op == BAM_CEQUAL) { + 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 < (int)l; ++n) { + uint8_t base = bam_seqi(ptr_seq, i); + if (p == pos) { + out << termcolor::bold; + } + if (!show_mod) { 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; } + } else { + if (mod_it != mod_end && i == mod_it->index) { + + 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; + } + } + + ++mod_it; + } else { + out << "_"; + } } i += 1; p += 1; + out << termcolor::reset; } - - } else if (op == BAM_CEQUAL) { - for (int n = 0; n < (int)l; ++n) { - uint8_t base = bam_seqi(ptr_seq, i); - switch (basemap[base]) { - case 65 : - std::cout << "A"; - break; - case 67 : - std::cout << "C"; - break; - case 71 : - std::cout << "G"; - break; - case 78 : - std::cout << "N"; - break; - case 84 : - std::cout << "T"; - break; +// p += l; + if (printed > max / 2) { + if (done_final_op) { + out << "..."; + break; + } else { + done_final_op = true; } - i += 1; } - p += l; - } else if (op == BAM_CDIFF) { - for (int n = 0; n < (int)l; ++n) { - 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; + } 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) { + p += l; } - i += 1; + i += l; + continue; } - p += l; - } else { - 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; + if (op == BAM_CINS && l > indel_length) { + out << termcolor::magenta << "[" << std::to_string(l) << "]" << termcolor::reset; + } + 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) { + char o = (op == BAM_CINS) ? 'I' : 'X'; + + 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; + } + } + + ++mod_it; + } else { + out << "_"; + } } i += 1; } - } - } - } - - 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"; + if (op == BAM_CDIFF) { + p += l; + printed += l; + } + if (printed > max) { + if (done_final_op) { + out << "..."; + break; + } else { + done_final_op = true; + } } - } 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); + } else { // soft-clips + if (k == 0) { + if (pos - p > max) { + i = l; + 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 + 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) { + 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 << "_"; + } + } + i += 1; + } + if (l > max && k > 0) { + out << "..."; } - 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, 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); - 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; + + 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; + 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 << "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, 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) { - read2sam(r, hdr, sam, low_mem); + 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); } - 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) { - ankerl::unordered_dense::map key_table; + void printKeyFromValue(int v, std::ostream& out) { + std::unordered_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; } } @@ -547,7 +799,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; } @@ -571,6 +823,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; } @@ -688,52 +943,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; } @@ -746,7 +1001,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 { @@ -758,27 +1013,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; @@ -791,8 +1046,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; @@ -804,11 +1059,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; } @@ -816,76 +1071,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) { @@ -909,23 +1164,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; @@ -949,71 +1204,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) { @@ -1035,27 +1290,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 adef7e0..e276d38 100644 --- a/src/term_out.h +++ b/src/term_out.h @@ -13,38 +13,41 @@ #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" 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, int pos, int indel_length, bool show_mod); - 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); } diff --git a/src/themes.cpp b/src/themes.cpp index 5d231e0..8880643 100644 --- a/src/themes.cpp +++ b/src/themes.cpp @@ -5,13 +5,13 @@ #include "menu.h" #include "themes.h" #include "glfw_keys.h" -#include "../include/defaultIni.hpp" -#include "../include/unordered_dense.h" +#include "defaultIni.hpp" +#include "ankerl_unordered_dense.h" namespace Themes { - BaseTheme::BaseTheme() { + EXPORT BaseTheme::BaseTheme() { fcCoverage.setStyle(SkPaint::kStrokeAndFill_Style); fcCoverage.setStrokeWidth(0); @@ -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; @@ -89,8 +90,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); @@ -133,22 +137,19 @@ namespace Themes { lcLabel.setStrokeWidth(1); lcLabel.setAntiAlias(true); -// lcBright.setStyle(SkPaint::kStrokeAndFill_Style); lcBright.setStyle(SkPaint::kStroke_Style); lcBright.setStrokeWidth(2); lcBright.setAntiAlias(true); - 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); + fcMarkers.setStrokeWidth(0.5); - marker_paint.setStyle(SkPaint::kStrokeAndFill_Style); - marker_paint.setAntiAlias(true); - marker_paint.setStrokeMiter(0.1); - marker_paint.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]; @@ -187,11 +188,56 @@ namespace Themes { } } - IgvTheme::IgvTheme() { + 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::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::fcMarkers: this->fcMarkers.setARGB(a, r, g, b); break; + case GwPaint::fcRoi: this->fcRoi.setARGB(a, r, g, b); break; + default: break; + } + } + + EXPORT IgvTheme::IgvTheme() { name = "igv"; 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); @@ -200,11 +246,15 @@ 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); fcN.setARGB(255, 128, 128, 128); + fcIns.setARGB(255, 158, 112, 250); + + fc5mc.setARGB(127, 194, 151, 58); +// 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); @@ -213,7 +263,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); @@ -222,12 +272,12 @@ namespace Themes { ecSplit.setStrokeWidth(1); } - DarkTheme::DarkTheme() { + EXPORT DarkTheme::DarkTheme() { name = "dark"; 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); @@ -236,12 +286,15 @@ 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); 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); + fc5hmc.setARGB(127, 215, 85, 23); lcJoins.setARGB(255, 142, 142, 142); lcLightJoins.setARGB(255, 82, 82, 82); lcLabel.setARGB(255, 182, 182, 182); @@ -250,7 +303,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); @@ -259,14 +312,14 @@ namespace Themes { ecSplit.setStrokeWidth(1); } - SlateTheme::SlateTheme() { + EXPORT SlateTheme::SlateTheme() { name = "slate"; 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); @@ -278,6 +331,10 @@ 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); + fc5hmc.setARGB(127, 215, 85, 23); lcJoins.setARGB(255, 142, 142, 142); lcLightJoins.setARGB(255, 82, 82, 82); lcLabel.setARGB(255, 182, 182, 182); @@ -286,7 +343,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); @@ -295,7 +352,7 @@ namespace Themes { ecSplit.setStrokeWidth(1); } - IniOptions::IniOptions() { + EXPORT IniOptions::IniOptions() { menu_level = ""; menu_table = MAIN; theme_str = "dark"; @@ -316,10 +373,12 @@ namespace Themes { start_index = 0; font_str = "Menlo"; font_size = 14; + mods_qual_threshold = 50; soft_clip_threshold = 20000; small_indel_threshold = 100000; snp_threshold = 1000000; + mod_threshold = 250000; edge_highlights = 100000; variant_distance = 100000; low_memory = 1500000; @@ -331,10 +390,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; @@ -354,35 +413,37 @@ namespace Themes { repeat_command=GLFW_KEY_R; } - void IniOptions::getOptionsFromIni() { + 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"; + } + } - ankerl::unordered_dense::map key_table; + void IniOptions::getOptionsFromIni() { + std::unordered_map key_table; 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"]); @@ -405,6 +466,12 @@ namespace Themes { if (myIni["general"].has("sv_arcs")) { sv_arcs = myIni["general"]["sv_arcs"] == "true"; } + if (myIni["general"].has("mods")) { + parse_mods = myIni["general"]["mods"] == "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"]); @@ -416,6 +483,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"]]; @@ -445,17 +517,119 @@ 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"] = ")"; + } + } + + 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(); + } + theme.name = theme_str; + } + 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 (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"]; + 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"]; + } } } @@ -481,12 +655,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); @@ -494,6 +668,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 { @@ -521,10 +696,125 @@ namespace Themes { return true; } + void IniOptions::saveIniChanges() { + mINI::INIFile file(ini_path); + file.generate(myIni); + } + + 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, + int screen_width, int screen_height) { + 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"].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; + count += 1; + } + count = 0; + for (const auto& item : track_paths) { + std::cout << " track " << std::endl; + seshIni["data"]["track" + std::to_string(count)] = item; + 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["show"]["region" + std::to_string(count)] = item.toString(); + count += 1; + } + seshIni["show"]["mode"] = (mode == 1) ? "tiled" : "single"; + count = 0; + for (auto& item: variant_paths_info) { + seshIni["show"]["var" + std::to_string(count)] = std::to_string(item.second); + count += 1; + } + + count = 0; + size_t last_refresh = 0; + 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)) { + seshIni["commands"][std::to_string(count)] = item; + } + } + if (item == "r" || item == "refresh") { + last_refresh = count; + } + count += 1; + } + keep = {"filter ", "find ", "f ", "colour", "color", "roi"}; + 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(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"; + 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); + 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); + 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"]; + 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}; - Fonts::Fonts() { + EXPORT Fonts::Fonts() { rect = SkRect::MakeEmpty(); path = SkPath(); fontMaxSize = 35; // in pixels @@ -586,4 +876,101 @@ 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, + 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] == '#') { + 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; + 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, SkColorGetR(acen), SkColorGetG(acen), SkColorGetB(acen), {}, name}; + } else if (property == "gvar") { + 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; + 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) { + 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 (...) { + + } + } 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) { + 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 472b6b1..3653b68 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 "export_definitions.h" namespace Themes { @@ -63,16 +63,28 @@ 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, 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, + fcMarkers, fc5mc, fc5hmc + }; + + class EXPORT BaseTheme { public: BaseTheme(); ~BaseTheme() = default; std::string name; // face colours - SkPaint bgPaint, fcNormal, fcDel, fcDup, fcInvF, fcInvR, fcTra, fcIns, fcSoftClip, \ - fcA, fcT, fcC, fcG, fcN, fcCoverage, fcTrack; - SkPaint fcNormal0, fcDel0, fcDup0, fcInvF0, fcInvR0, fcTra0, fcSoftClip0, fcBigWig; + 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, fc5mc, fc5hmc; std::vector mate_fc; std::vector mate_fc0; @@ -84,57 +96,59 @@ namespace Themes { float lwMateUnmapped, lwSplit, lwCoverage; // line colours and Insertion paint - SkPaint lcJoins, lcCoverage, lcLightJoins, insF, insS, lcLabel, lcBright; + SkPaint lcJoins, lcCoverage, lcLightJoins, lcLabel, lcBright; // text colours SkPaint tcDel, tcIns, tcLabels, tcBackground; // Markers - SkPaint marker_paint; + SkPaint fcMarkers; uint8_t alpha, mapq0_alpha; std::array, 16> BasePaints; void setAlphas(); + void setPaintARGB(int paint_enum, int alpha, int red, int green, int blue); }; - 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 IniOptions { + class EXPORT IniOptions { public: IniOptions(); ~IniOptions() {}; - mINI::INIStructure myIni; - ankerl::unordered_dense::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; 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; - bool no_show, log2_cov, tlen_yscale, expand_tracks, vcf_as_tracks, sv_arcs; + 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; int scroll_left; @@ -151,17 +165,25 @@ 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; 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::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, int screen_width, int screen_height); }; - class Fonts { + class EXPORT Fonts { public: Fonts(); ~Fonts() = default; @@ -178,4 +200,13 @@ 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, + Themes::BaseTheme &theme); + } diff --git a/src/utils.cpp b/src/utils.cpp index 3f2a00e..298c780 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" @@ -169,13 +169,21 @@ 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; } } - Region parseRegion(std::string &s) { + EXPORT Region parseRegion(std::string &s) { Region reg; std::string s2; if (s.find(":") != std::string::npos) { @@ -186,8 +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 { @@ -201,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) { @@ -363,20 +373,26 @@ 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'); 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 53c62bf..b833c1d 100644 --- a/src/utils.h +++ b/src/utils.h @@ -7,8 +7,9 @@ #include #include #include -#include "../include/unordered_dense.h" - +#include "ankerl_unordered_dense.h" +#include "export_definitions.h" +#include "ideogram.h" #include "htslib/faidx.h" #if defined(_WIN32) @@ -37,7 +38,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; @@ -55,10 +56,14 @@ namespace Utils { coding_end = -1; value = 0; level = 0; + start = 0; + end = 0; + strand = 0; + anyToDraw = false; } }; - class GFFTrackBlock { + class EXPORT GFFTrackBlock { public: std::string chrom, name, line, vartype; std::vector parts; @@ -66,23 +71,29 @@ namespace Utils { int strand; // 0 is none, 1 forward, 2 reverse }; - class Region { + class EXPORT Region { public: 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 std::vector featureLevels; Region() { chrom = ""; start = -1; end = -1; + markerPos = -1; + markerPosEnd = -1; + chromLength = 0; refSeq = nullptr; } + std::string toString(); }; - 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); @@ -96,11 +107,11 @@ namespace Utils { FileNameInfo parseFilenameInfo(std::filesystem::path &path); - struct Dims { + struct EXPORT Dims { int x, y; }; - Dims parseDimensions(std::string &s); + EXPORT_FUNCTION Dims parseDimensions(std::string &s); int intervalOverlap(int start1, int end1, int start2, int end2); @@ -112,7 +123,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; @@ -151,4 +162,5 @@ namespace Utils { void ltrim(std::string &s); void rtrim(std::string &s); void trim(std::string &s); + } diff --git a/test/chr20_13822936-13833912.mod.bam b/test/chr20_13822936-13833912.mod.bam new file mode 100644 index 0000000..18e4898 Binary files /dev/null and b/test/chr20_13822936-13833912.mod.bam differ diff --git a/test/chr20_13822936-13833912.mod.bam.bai b/test/chr20_13822936-13833912.mod.bam.bai new file mode 100644 index 0000000..3ddad28 Binary files /dev/null and b/test/chr20_13822936-13833912.mod.bam.bai differ 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