├── .gitignore ├── CATG.py ├── LICENSE ├── Manual └── CATG User Guide.pdf ├── README.md ├── coll_asm_corr_gui ├── __init__.py ├── corrector │ ├── __init__.py │ ├── corrector.py │ ├── locator.py │ └── vis.py ├── io │ ├── __init__.py │ └── file_reader.py ├── main │ ├── __init__.py │ ├── assembly_corrector_main.py │ └── file_loader_dialog.py ├── resources │ ├── CATG.icns │ ├── CATG.ico │ ├── CATG.png │ ├── assembly_corrector_main.ui │ └── file_loader_dialog.ui └── ui │ ├── __init__.py │ ├── custom_control.py │ ├── ui_assembly_corrector_main.py │ └── ui_file_loader_dialog.py └── test └── test_data.tar.gz /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .vscode 3 | .idea 4 | *.pyc 5 | __pycache__ 6 | venv 7 | *.exe 8 | *.build 9 | *.dist 10 | *.onefile-build 11 | *.app -------------------------------------------------------------------------------- /CATG.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import sys 3 | from os import path 4 | from PySide6.QtWidgets import QApplication 5 | from PySide6.QtGui import QIcon 6 | from PySide6.QtCore import QCoreApplication, Qt 7 | from qt_material import apply_stylesheet 8 | from coll_asm_corr_gui.main.assembly_corrector_main import AssemblyCorrectorMain 9 | 10 | 11 | if __name__ == "__main__": 12 | QCoreApplication.setAttribute(Qt.AA_ShareOpenGLContexts) 13 | app = QApplication([]) 14 | icon_path = path.join(path.dirname(path.abspath(__file__)), "coll_asm_corr_gui/resources/CATG.png") 15 | app.setWindowIcon(QIcon(icon_path)) 16 | main_window = AssemblyCorrectorMain() 17 | apply_stylesheet(app, theme="dark_teal.xml") 18 | main_window.show() 19 | sys.exit(app.exec()) 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2022, Shengcheng Zhang 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /Manual/CATG User Guide.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sc-zhang/CATG/606d7f23fe713f445e2af61d3766958bc2dfa072/Manual/CATG User Guide.pdf -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CATG: A Tool for Correcting Genome Assembly with Collinearity 2 | 3 | [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.13621059.svg)](https://doi.org/10.5281/zenodo.13621059) 4 | 5 | # Introduction 6 | CATG (**C**ollinearity-based **A**ssembly correc**T**or **G**UI) is a GUI application base on Qt with PySide6. 7 | It is a tool that can adjust assembly with collinearity and generate tour files for assembly. 8 | 9 | # How to cite 10 | > CATG is now published in G3 Genes|Genomes|Genetics 11 | > Shengcheng Zhang, Hejun Du, Xingtan Zhang, Binzhong Wang, Collinearity-based Assembly Correction Tool GUI: Software for collinearity-based genome assembly correction, G3 Genes|Genomes|Genetics, Volume 15, Issue 2, February 2025, jkae277, [https://doi.org/10.1093/g3journal/jkae277](https://doi.org/10.1093/g3journal/jkae277). 12 | 13 | 14 | # Installation 15 | ## Download pre-build binary files 16 | 17 | User can download executable file with following links. 18 | 19 | 1. Windows user 20 | - https://github.com/sc-zhang/CATG/releases/download/v1.2.2/CATG-v1.2.2.exe 21 | - https://zenodo.org/records/13621059/files/CATG-v1.2.2.exe?download=1 22 | 23 | 2. Mac user (Apple silicon) 24 | - https://github.com/sc-zhang/CATG/releases/download/v1.2.2/CATG-v1.2.2.arm.dmg 25 | - https://zenodo.org/records/13621059/files/CATG-v1.2.2.arm.dmg?download=1 26 | 27 | 3. Mac user (Intel silicon) 28 | - https://github.com/sc-zhang/CATG/releases/download/v1.2.2/CATG-v1.2.2.Intel.dmg 29 | - https://zenodo.org/records/13621059/files/CATG-v1.2.2.Intel.dmg?download=1 30 | 31 | 4. Ubuntu user 32 | - https://github.com/sc-zhang/CATG/releases/download/v1.2.2/CATG-v1.2.2.bin 33 | - https://zenodo.org/records/13621059/files/CATG-v1.2.2.bin?download=1 34 | 35 | # Usage 36 | 37 | The online version of user manual could be found in [CATG Wiki](https://github.com/sc-zhang/CATG/wiki) 38 | 39 | The pdf version of user manual could be found in [CATG User Guide](https://github.com/sc-zhang/CATG/releases/download/v1.2.2/CATG.User.Guide.pdf) 40 | 41 | # Test data 42 | 43 | 1. Test data could be found in following links. 44 | - https://github.com/sc-zhang/CATG/archive/refs/tags/v1.2.2.zip 45 | - https://zenodo.org/records/13621059/files/sc-zhang/CATG-v1.2.2.zip?download=1 46 | 47 | 2. Unzip this compressed file, a file named "test.tar.gz" could be found in test folder, then unzip test.tar.gz, four files: qry.agp, qry.bed, qry.ref.anchors, ref.bed could be found which can be used with this tool. 48 | 49 | 3. test.tar.gz also could be found in following links. 50 | - https://github.com/sc-zhang/CATG/releases/download/v1.2.2/test_data.tar.gz 51 | -------------------------------------------------------------------------------- /coll_asm_corr_gui/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sc-zhang/CATG/606d7f23fe713f445e2af61d3766958bc2dfa072/coll_asm_corr_gui/__init__.py -------------------------------------------------------------------------------- /coll_asm_corr_gui/corrector/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sc-zhang/CATG/606d7f23fe713f445e2af61d3766958bc2dfa072/coll_asm_corr_gui/corrector/__init__.py -------------------------------------------------------------------------------- /coll_asm_corr_gui/corrector/corrector.py: -------------------------------------------------------------------------------- 1 | class Corrector: 2 | 3 | def __init__(self): 4 | pass 5 | 6 | @staticmethod 7 | def get_gene_ctg(chr_list, sp, ep): 8 | infos = [] 9 | for info in chr_list: 10 | chsp = info[0] 11 | chep = info[1] 12 | ovlp = min(chep, ep) - max(chsp, sp) 13 | if ovlp >= 0: 14 | infos.append([ovlp, info]) 15 | if infos: 16 | return sorted(infos, reverse=True)[0][1] 17 | else: 18 | return [] 19 | 20 | @staticmethod 21 | def trans_anno(ori_agp_db, adj_agp_db, ori_bed_db): 22 | adj_bed_db = {} 23 | 24 | cov_adj_agp_db = {} 25 | for chrn in adj_agp_db: 26 | for sp, ep, ctg, ctg_len, direct in adj_agp_db[chrn]: 27 | cov_adj_agp_db[ctg] = [chrn, sp, ep, ctg_len, direct] 28 | 29 | for gid in sorted(ori_bed_db): 30 | chrn, gsp, gep, gdir = ori_bed_db[gid] 31 | 32 | match_ctg = Corrector.get_gene_ctg(ori_agp_db[chrn], gsp, gep) 33 | 34 | if match_ctg: 35 | csp, cep, tig, _, tdir = match_ctg 36 | if tig not in cov_adj_agp_db: 37 | continue 38 | nchrn, nsp, nep, nctg_len, ndir = cov_adj_agp_db[tig] 39 | 40 | if tdir == '+': 41 | gts = gsp - csp + 1 42 | gte = gep - csp + 1 43 | else: 44 | gts = cep - gep + 1 45 | gte = cep - gsp + 1 46 | if gdir == tdir: 47 | gtd = '+' 48 | else: 49 | gtd = '-' 50 | if ndir == '+': 51 | gns = nsp + gts - 1 52 | gne = nsp + gte - 1 53 | else: 54 | gns = nep - gte + 1 55 | gne = nep - gts + 1 56 | if gtd == ndir: 57 | gnd = '+' 58 | else: 59 | gnd = '-' 60 | if gns <= 0 or gne <= 0: 61 | continue 62 | adj_bed_db[gid] = [nchrn, gns, gne, gnd] 63 | 64 | return adj_bed_db 65 | 66 | @staticmethod 67 | def reverse_chr(agp_list): 68 | adj_agp_list = [] 69 | base = 1 70 | for _, _, ctg, ctg_len, direct in agp_list[::-1]: 71 | if direct == '+': 72 | direct = '-' 73 | else: 74 | direct = '+' 75 | adj_agp_list.append([base, base+ctg_len-1, ctg, ctg_len, direct]) 76 | base += ctg_len+100 77 | return adj_agp_list 78 | 79 | @staticmethod 80 | def reverse_block(agp_list, pos): 81 | start_idx, _, end_idx, _ = pos 82 | adj_agp_list = [] 83 | base = 1 84 | 85 | for _ in agp_list[:start_idx]: 86 | adj_agp_list.append(_) 87 | base += _[3]+100 88 | 89 | for i in range(end_idx, start_idx-1, -1): 90 | _, _, ctg, ctg_len, direct = agp_list[i] 91 | if direct == '+': 92 | direct = '-' 93 | else: 94 | direct = '+' 95 | adj_agp_list.append([base, base+ctg_len-1, ctg, ctg_len, direct]) 96 | base += ctg_len+100 97 | 98 | for _ in agp_list[end_idx+1:]: 99 | adj_agp_list.append(_) 100 | base += _[3]+100 101 | 102 | return adj_agp_list 103 | 104 | @staticmethod 105 | def split_block(agp_list, pos): 106 | start_idx, _, end_idx, _ = pos 107 | adj_agp_list = [] 108 | 109 | base = 1 110 | 111 | for _ in agp_list[:start_idx]: 112 | adj_agp_list.append(_) 113 | base += _[3]+100 114 | 115 | extract_agp_list = agp_list[start_idx: end_idx+1] 116 | 117 | for _ in agp_list[end_idx + 1:]: 118 | _[0] = base 119 | _[1] = base+_[3]-1 120 | adj_agp_list.append(_) 121 | base += _[3]+100 122 | 123 | return adj_agp_list, extract_agp_list 124 | 125 | @staticmethod 126 | def ins_term(agp_list, ins_agp_list, ins_head=True): 127 | adj_agp_list = [] 128 | base = 1 129 | if ins_head: 130 | order = [ins_agp_list, agp_list] 131 | else: 132 | order = [agp_list, ins_agp_list] 133 | for cur_list in order: 134 | for _ in cur_list: 135 | _[0] = base 136 | _[1] = base+_[3]-1 137 | adj_agp_list.append(_) 138 | base += _[3]+100 139 | return adj_agp_list 140 | 141 | @staticmethod 142 | def ins_pos(agp_list, ins_agp_list, pos, ins_before=True): 143 | adj_agp_list = [] 144 | base = 1 145 | if ins_before: 146 | ins_pos = pos[0] 147 | order = [agp_list[:ins_pos], ins_agp_list, agp_list[ins_pos:]] 148 | else: 149 | ins_pos = pos[2] 150 | order = [agp_list[:ins_pos+1], ins_agp_list, agp_list[ins_pos+1:]] 151 | for cur_list in order: 152 | for _ in cur_list: 153 | _[0] = base 154 | _[1] = base+_[3]-1 155 | adj_agp_list.append(_) 156 | base += _[3]+100 157 | return adj_agp_list 158 | 159 | @staticmethod 160 | def swap_blk_diff_chr(src_list, src_pos, tgt_list, tgt_pos): 161 | src_start_idx, _, src_end_idx, _ = src_pos 162 | tgt_start_idx, _, tgt_end_idx, _ = tgt_pos 163 | 164 | src_base = 1 165 | tgt_base = 1 166 | 167 | src_adj_agp_list = [] 168 | tgt_adj_agp_list = [] 169 | 170 | for _ in src_list[:src_start_idx]: 171 | src_adj_agp_list.append(_) 172 | src_base += _[3]+100 173 | 174 | for _ in tgt_list[:tgt_start_idx]: 175 | tgt_adj_agp_list.append(_) 176 | tgt_base += _[3]+100 177 | 178 | for _ in src_list[src_start_idx: src_end_idx+1]: 179 | _[0] = tgt_base 180 | _[1] = tgt_base+_[3]-1 181 | tgt_adj_agp_list.append(_) 182 | tgt_base += _[3]+100 183 | 184 | for _ in tgt_list[tgt_start_idx: tgt_end_idx+1]: 185 | _[0] = src_base 186 | _[1] = src_base+_[3]-1 187 | src_adj_agp_list.append(_) 188 | src_base += _[3]+100 189 | 190 | for _ in src_list[src_end_idx+1:]: 191 | _[0] = src_base 192 | _[1] = src_base+_[3]-1 193 | src_adj_agp_list.append(_) 194 | src_base += _[3]+100 195 | 196 | for _ in tgt_list[tgt_end_idx+1:]: 197 | _[0] = tgt_base 198 | _[1] = tgt_base+_[3]-1 199 | tgt_adj_agp_list.append(_) 200 | tgt_base += _[3]+100 201 | 202 | return src_adj_agp_list, tgt_adj_agp_list 203 | 204 | @staticmethod 205 | def swap_blk_single_chr(agp_list, src_pos, tgt_pos): 206 | src_start_idx, _, src_end_idx, _ = src_pos 207 | tgt_start_idx, _, tgt_end_idx, _ = tgt_pos 208 | if src_start_idx > tgt_start_idx: 209 | src_start_idx, tgt_start_idx = tgt_start_idx, src_start_idx 210 | src_end_idx, tgt_end_idx = tgt_end_idx, src_end_idx 211 | 212 | if tgt_start_idx < src_end_idx: 213 | tgt_start_idx = src_end_idx + 1 214 | order = [agp_list[:src_start_idx], 215 | agp_list[tgt_start_idx: tgt_end_idx+1], 216 | agp_list[src_end_idx+1: tgt_start_idx], 217 | agp_list[src_start_idx: src_end_idx+1], 218 | agp_list[tgt_end_idx+1:]] 219 | 220 | adj_agp_list = [] 221 | base = 1 222 | for cur_list in order: 223 | for _ in cur_list: 224 | _[0] = base 225 | _[1] = base + _[3] - 1 226 | adj_agp_list.append(_) 227 | base += _[3] + 100 228 | return adj_agp_list 229 | 230 | @staticmethod 231 | def remove_blk(agp_list, pos): 232 | start_idx, _, end_idx, _ = pos 233 | adj_agp_list = [] 234 | 235 | base = 1 236 | 237 | for _ in agp_list[:start_idx]: 238 | adj_agp_list.append(_) 239 | base += _[3] + 100 240 | 241 | for _ in agp_list[end_idx + 1:]: 242 | _[0] = base 243 | _[1] = base + _[3] - 1 244 | adj_agp_list.append(_) 245 | base += _[3] + 100 246 | 247 | return adj_agp_list 248 | -------------------------------------------------------------------------------- /coll_asm_corr_gui/corrector/locator.py: -------------------------------------------------------------------------------- 1 | from math import sqrt 2 | 3 | 4 | def euc_dist(a, b): 5 | return sqrt((b[0] - a[0]) ** 2 + (b[1] - a[1]) ** 2) 6 | 7 | 8 | class Locator: 9 | 10 | def __init__(self): 11 | self.links = [] 12 | self.block_db = {} 13 | 14 | def convert_anchors(self, qry_db, ref_db, anc_list): 15 | link_db = {} 16 | 17 | for qry_gene, ref_gene in anc_list: 18 | if qry_gene not in qry_db or ref_gene not in ref_db: 19 | continue 20 | qchr, qsp, qep, _ = qry_db[qry_gene] 21 | qp = min(qsp, qep) 22 | schr, ssp, sep, _ = ref_db[ref_gene] 23 | sp = min(ssp, sep) 24 | if qchr not in link_db: 25 | link_db[qchr] = {} 26 | if schr not in link_db[qchr]: 27 | link_db[qchr][schr] = [] 28 | link_db[qchr][schr].append([qp, sp]) 29 | 30 | for chrx in sorted(link_db): 31 | for chry in sorted(link_db[chrx]): 32 | for x, y in sorted(link_db[chrx][chry]): 33 | self.links.append([chrx, x, chry, y]) 34 | 35 | def get_break_blocks(self, resolution): 36 | link_db = {} 37 | chr_len_db = {} 38 | for qchr, qp, schr, sp in self.links: 39 | if qchr not in link_db: 40 | link_db[qchr] = {} 41 | if schr not in link_db[qchr]: 42 | link_db[qchr][schr] = [] 43 | link_db[qchr][schr].append([qp, sp]) 44 | if qchr not in chr_len_db: 45 | chr_len_db[qchr] = {} 46 | if schr not in chr_len_db[qchr]: 47 | chr_len_db[qchr][schr] = [0, 0] 48 | if qp > chr_len_db[qchr][schr][0]: 49 | chr_len_db[qchr][schr][0] = qp 50 | if sp > chr_len_db[qchr][schr][1]: 51 | chr_len_db[qchr][schr][1] = sp 52 | 53 | for qchr in link_db: 54 | self.block_db[qchr] = {} 55 | for schr in link_db[qchr]: 56 | groups = [] 57 | chr_len_merge = euc_dist([0, 0], chr_len_db[qchr][schr]) 58 | for x, y in link_db[qchr][schr]: 59 | if len(groups) == 0: 60 | groups.append([[x, y]]) 61 | else: 62 | is_add = False 63 | for i in range(0, len(groups)): 64 | tail_x, tail_y = groups[i][-1] 65 | if euc_dist([x, y], [tail_x, tail_y]) * resolution / chr_len_merge < 1: 66 | groups[i].append([x, y]) 67 | is_add = True 68 | break 69 | if not is_add: 70 | groups.append([[x, y]]) 71 | 72 | self.block_db[qchr][schr] = [] 73 | for group in groups: 74 | x = [] 75 | y = [] 76 | for i in range(0, len(group)): 77 | x.append(group[i][0]) 78 | y.append(group[i][1]) 79 | min_y = min(y) 80 | max_y = max(y) 81 | min_index = y.index(min_y) 82 | max_index = y.index(max_y) 83 | min_x = x[min_index] 84 | max_x = x[max_index] 85 | sx, sy = group[0] 86 | ex, ey = group[-1] 87 | if min_x > max_x: 88 | tmp = min_x 89 | min_x = max_x 90 | max_x = tmp 91 | tmp = min_y 92 | min_y = max_y 93 | max_y = tmp 94 | tmp_list = [] 95 | tmp_list.extend([sx, sy]) 96 | if sx < min_x < ex: 97 | tmp_list.extend([min_x, min_y]) 98 | if sx < max_x < ex: 99 | tmp_list.extend([max_x, max_y]) 100 | tmp_list.extend([ex, ey]) 101 | for i in range(0, len(tmp_list) - 2, 2): 102 | self.block_db[qchr][schr].append([tmp_list[i], tmp_list[i + 1], tmp_list[i + 2], tmp_list[i + 3]]) 103 | -------------------------------------------------------------------------------- /coll_asm_corr_gui/corrector/vis.py: -------------------------------------------------------------------------------- 1 | import matplotlib 2 | matplotlib.use("qt5agg") 3 | from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas 4 | import matplotlib.backends.backend_pdf 5 | import matplotlib.pyplot as plt 6 | from coll_asm_corr_gui.corrector.locator import euc_dist 7 | 8 | 9 | class VisCanvas(FigureCanvas): 10 | 11 | def __init__(self): 12 | fig = plt.figure(figsize=(10, 10), dpi=300, tight_layout=True, facecolor="#FFF2E2") 13 | self.plt = plt 14 | super(VisCanvas, self).__init__(fig) 15 | 16 | 17 | class VisContent: 18 | 19 | def __init__(self): 20 | self.chr_list_x = [] 21 | self.chr_list_y = [] 22 | self.block_list_db = {} 23 | self.block_detail = [] 24 | self.data_db = {} 25 | self.block_regions = [] 26 | self.figure_content = VisCanvas() 27 | 28 | def convert_link(self, links): 29 | chr_set_x = set() 30 | chr_set_y = set() 31 | self.data_db = {} 32 | for chr_x, pos_x, chr_y, pos_y in links: 33 | chr_set_x.add(chr_x) 34 | chr_set_y.add(chr_y) 35 | if chr_x not in self.data_db: 36 | self.data_db[chr_x] = {} 37 | if chr_y not in self.data_db[chr_x]: 38 | self.data_db[chr_x][chr_y] = [] 39 | self.data_db[chr_x][chr_y].append([pos_x, pos_y]) 40 | 41 | self.chr_list_x = sorted(chr_set_x) 42 | self.chr_list_y = sorted(chr_set_y) 43 | 44 | @staticmethod 45 | def __get_ctg_pos(region, pos): 46 | s = 0 47 | e = len(region) - 1 48 | while s <= e: 49 | mid = int((s + e) / 2) 50 | if region[mid][0] > pos: 51 | e = mid - 1 52 | elif region[mid][0] < pos: 53 | s = mid + 1 54 | else: 55 | return mid 56 | if region[e][1] >= pos: 57 | return e 58 | elif e == len(region) - 1: 59 | return e 60 | else: 61 | return -1 62 | 63 | def gen_figure(self, links, block_db, agp_db, resolution, qry_name="", ref_name=""): 64 | self.convert_link(links) 65 | chr_len_db = {} 66 | 67 | for chrx in self.chr_list_x: 68 | if chrx not in chr_len_db: 69 | chr_len_db[chrx] = 0 70 | for chry in self.chr_list_y: 71 | if chry not in chr_len_db: 72 | chr_len_db[chry] = 0 73 | if chry not in self.data_db[chrx]: 74 | continue 75 | for x, y in self.data_db[chrx][chry]: 76 | if x > chr_len_db[chrx]: 77 | chr_len_db[chrx] = x 78 | if y > chr_len_db[chry]: 79 | chr_len_db[chry] = y 80 | base_x = 0 81 | base_y = 0 82 | offset_db = {} 83 | for chrx in self.chr_list_x: 84 | offset_db[chrx] = base_x 85 | base_x += chr_len_db[chrx] 86 | for chry in self.chr_list_y: 87 | offset_db[chry] = base_y 88 | base_y += chr_len_db[chry] 89 | 90 | data_x = [] 91 | data_y = [] 92 | for chrx in self.chr_list_x: 93 | for chry in self.chr_list_y: 94 | if chry not in self.data_db[chrx]: 95 | continue 96 | for x, y in self.data_db[chrx][chry]: 97 | data_x.append(x + offset_db[chrx]) 98 | data_y.append(y + offset_db[chry]) 99 | 100 | block_x = [] 101 | block_y = [] 102 | 103 | idx = 0 104 | 105 | self.block_list_db = {} 106 | self.block_detail = [] 107 | self.block_regions = [] 108 | 109 | for chrx in self.chr_list_x: 110 | for chry in self.chr_list_y: 111 | if chrx not in block_db or chry not in block_db[chrx]: 112 | continue 113 | for x1, y1, x2, y2 in block_db[chrx][chry]: 114 | if euc_dist([x1, y1], [x2, y2]) * 1. * resolution / \ 115 | euc_dist([0, 0], [chr_len_db[chrx], chr_len_db[chry]]) < 1: 116 | continue 117 | 118 | idx += 1 119 | if chrx not in self.block_list_db: 120 | self.block_list_db[chrx] = [] 121 | self.block_list_db[chrx].append(str(idx)) 122 | 123 | block_x.append([x1 + offset_db[chrx], x2 + offset_db[chrx]]) 124 | rstart = self.__get_ctg_pos(agp_db[chrx], x1) 125 | rend = self.__get_ctg_pos(agp_db[chrx], x2) 126 | if rstart == -1 or rend == -1: 127 | continue 128 | self.block_detail.append([]) 129 | for _ in range(rstart, rend+1): 130 | self.block_detail[-1].append(agp_db[chrx][_][2]+agp_db[chrx][_][4]) 131 | ctg1 = agp_db[chrx][rstart][2] 132 | ctg2 = agp_db[chrx][rend][2] 133 | self.block_regions.append([rstart, ctg1, rend, ctg2]) 134 | block_y.append([y1 + offset_db[chry], y2 + offset_db[chry]]) 135 | 136 | max_x = 0 137 | max_y = 0 138 | for chrx in self.chr_list_x: 139 | max_x += chr_len_db[chrx] 140 | 141 | for chry in self.chr_list_y: 142 | max_y += chr_len_db[chry] 143 | 144 | x_ticks = [] 145 | x_labels = [] 146 | base_x = 0 147 | chrx_idx_db = {} 148 | idx = 0 149 | offset_x_list = [] 150 | 151 | self.figure_content.plt.clf() 152 | 153 | # draw grid lines 154 | for chrx in self.chr_list_x: 155 | offset_x_list.append(base_x) 156 | chrx_idx_db[chrx] = idx 157 | idx += 1 158 | self.figure_content.plt.plot([chr_len_db[chrx] + base_x, chr_len_db[chrx] + base_x], [0, max_y], 159 | linestyle='-', 160 | color='green', 161 | linewidth=0.5, markersize=0) 162 | x_ticks.append(base_x + int(chr_len_db[chrx] / 2)) 163 | x_labels.append(chrx) 164 | base_x += chr_len_db[chrx] 165 | offset_x_list.append(base_x) 166 | 167 | y_ticks = [] 168 | y_labels = [] 169 | base_y = 0 170 | chry_idx_db = {} 171 | idx = 0 172 | offset_y_list = [] 173 | 174 | for chry in self.chr_list_y: 175 | offset_y_list.append(base_y) 176 | chry_idx_db[chry] = idx 177 | idx += 1 178 | self.figure_content.plt.plot([0, max_x], [chr_len_db[chry] + base_y, chr_len_db[chry] + base_y], 179 | linestyle='-', 180 | color='green', 181 | linewidth=0.5, markersize=0) 182 | y_ticks.append(base_y + int(chr_len_db[chry] / 2)) 183 | y_labels.append(chry) 184 | base_y += chr_len_db[chry] 185 | offset_y_list.append(base_y) 186 | 187 | # draw dotplot 188 | down_sample_dist = max(int(len(data_x) / 1500), 1) 189 | self.figure_content.plt.plot(data_x[::down_sample_dist], data_y[::down_sample_dist], 190 | linestyle='', color='black', marker='o', markersize=0.5) 191 | 192 | # draw blocks and block ids 193 | for i in range(0, len(block_x)): 194 | block_pos = [(block_x[i][0] + block_x[i][1]) / 2.0, (block_y[i][0] + block_y[i][1]) / 2.0] 195 | 196 | self.figure_content.plt.plot(block_x[i], block_y[i], linestyle='-', color='orange', linewidth=0.5, 197 | markersize=0) 198 | self.figure_content.plt.annotate(i+1, 199 | xy=block_pos, 200 | bbox=dict( 201 | boxstyle="circle,pad=0", 202 | fc="white", 203 | ec="black", 204 | alpha=0.5, 205 | lw=1 206 | ), 207 | fontsize=8, color='blue', ha='right') 208 | 209 | self.figure_content.plt.xlim([0, max_x]) 210 | self.figure_content.plt.ylim([0, max_y]) 211 | self.figure_content.plt.xticks(x_ticks) 212 | self.figure_content.plt.yticks(y_ticks) 213 | self.figure_content.plt.xlabel(qry_name) 214 | self.figure_content.plt.ylabel(ref_name) 215 | ax = self.figure_content.plt.gca() 216 | ax.set_facecolor("#FFF2E2") 217 | ax.set_xticklabels(x_labels, rotation=45) 218 | ax.set_yticklabels(y_labels, rotation=0) 219 | ax.xaxis.set_ticks_position('top') 220 | ax.yaxis.set_ticks_position('right') 221 | ax.invert_yaxis() 222 | ax.tick_params(top=False, right=False) 223 | -------------------------------------------------------------------------------- /coll_asm_corr_gui/io/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sc-zhang/CATG/606d7f23fe713f445e2af61d3766958bc2dfa072/coll_asm_corr_gui/io/__init__.py -------------------------------------------------------------------------------- /coll_asm_corr_gui/io/file_reader.py: -------------------------------------------------------------------------------- 1 | class Reader: 2 | 3 | def __init__(self): 4 | pass 5 | 6 | @staticmethod 7 | def read_bed(bed_file): 8 | dict = {} 9 | chr_set = set() 10 | with open(bed_file, 'r') as fin: 11 | for line in fin: 12 | if len(line.strip()) == 0 or line[0] == '#': 13 | continue 14 | data = line.strip().split() 15 | if len(data) < 4: 16 | continue 17 | chrn = data[0] 18 | chr_set.add(chrn) 19 | try: 20 | sp = int(data[1]) 21 | ep = int(data[2]) 22 | except ValueError: 23 | return None, None 24 | direct = '+' 25 | if sp > ep: 26 | sp, ep = ep, sp 27 | direct = '-' 28 | gene = data[3] 29 | dict[gene] = [chrn, sp, ep, direct] 30 | return sorted(chr_set), dict 31 | 32 | @staticmethod 33 | def read_agp(in_agp): 34 | dict = {} 35 | with open(in_agp, 'r') as fin: 36 | for line in fin: 37 | data = line.strip().split() 38 | if len(line.strip()) == 0 or line[0] == '#' or data[4] == 'U' or len(data) < 8: 39 | continue 40 | chr_x = data[0] 41 | try: 42 | sp = int(float(data[1])) 43 | ep = int(float(data[2])) 44 | ctg_len = int(data[7]) 45 | except ValueError: 46 | return None 47 | ctg = data[5] 48 | direct = data[-1] 49 | if chr_x not in dict: 50 | dict[chr_x] = [] 51 | dict[chr_x].append([sp, ep, ctg, ctg_len, direct]) 52 | return dict 53 | 54 | @staticmethod 55 | def read_anchors(in_anchors): 56 | gene_pairs = [] 57 | with open(in_anchors, 'r') as fin: 58 | for line in fin: 59 | if len(line.strip()) == 0 or line[0] == '#': 60 | continue 61 | data = line.strip().split() 62 | if len(data) < 2: 63 | return None 64 | gene_pairs.append([data[0], data[1]]) 65 | return gene_pairs 66 | -------------------------------------------------------------------------------- /coll_asm_corr_gui/main/__init__.py: -------------------------------------------------------------------------------- 1 | import coll_asm_corr_gui.main.assembly_corrector_main 2 | import coll_asm_corr_gui.main.file_loader_dialog 3 | -------------------------------------------------------------------------------- /coll_asm_corr_gui/main/assembly_corrector_main.py: -------------------------------------------------------------------------------- 1 | from os import path 2 | import sys 3 | from coll_asm_corr_gui.main import file_loader_dialog 4 | from coll_asm_corr_gui.io import file_reader 5 | from coll_asm_corr_gui.ui import ui_assembly_corrector_main, custom_control 6 | from coll_asm_corr_gui.corrector import locator, vis, corrector 7 | from copy import deepcopy 8 | from traceback import format_exc 9 | from PySide6.QtWidgets import QWidget, QFileDialog, QMessageBox 10 | from PySide6.QtCore import QCoreApplication, QEventLoop 11 | 12 | 13 | class OptArgs: 14 | 15 | def __init__(self): 16 | self.src_chr = "" 17 | self.src_blk = "" 18 | self.tgt_chr = "" 19 | self.tgt_blk = "" 20 | self.is_rev = False 21 | 22 | 23 | class AssemblyCorrectorMain(QWidget): 24 | 25 | def __init__(self): 26 | super(AssemblyCorrectorMain, self).__init__() 27 | self.ui = None 28 | self.main_window = None 29 | self.graph_scene = None 30 | 31 | self.opt_method_db = { 32 | "Insert front": self.__ins_front, 33 | "Insert back": self.__ins_back, 34 | "Insert head": self.__ins_head, 35 | "Insert tail": self.__ins_tail, 36 | "Source chromosome": self.__rev_chr, 37 | "Source block": self.__rev_blk, 38 | "Swap chromosome": self.__swp_chr, 39 | "Swap block": self.__swp_blk, 40 | "Delete block": self.__remove_blk 41 | } 42 | self.opt_method_info = { 43 | "Insert front": "Inserting", 44 | "Insert back": "Inserting", 45 | "Insert head": "Inserting", 46 | "Insert tail": "Inserting", 47 | "Source chromosome": "Reversing", 48 | "Source block": "Reversing", 49 | "Swap chromosome": "Swapping", 50 | "Swap block": "Swapping", 51 | "Delete block": "Removing" 52 | } 53 | # File paths and names 54 | self.qry_bed_file = "" 55 | self.ref_bed_file = "" 56 | self.anchors_file = "" 57 | self.qry_agp_file = "" 58 | self.qry_name = "" 59 | self.ref_name = "" 60 | 61 | # Datas 62 | self.qry_chr_list = None 63 | self.qry_bed_db = None 64 | self.last_bed_db = None 65 | self.ref_bed_db = None 66 | self.gene_pairs = None 67 | self.qry_agp_db = None 68 | self.last_agp_db = None 69 | self.block_regions = None 70 | self.block_list_db = None 71 | self.block_detail = None 72 | 73 | # mpl_vis for generate collinearity figure 74 | self.mpl_vis = vis.VisContent() 75 | 76 | # corrector for all adjust operations 77 | self.corrector = corrector.Corrector() 78 | 79 | # reader for data 80 | self.reader = file_reader.Reader() 81 | 82 | self.__init_ui() 83 | 84 | def __init_ui(self): 85 | self.ui = ui_assembly_corrector_main.Ui_AssemblyCorrectorMain() 86 | self.ui.setupUi(self) 87 | 88 | self.ui.method_cbox.addItems(self.opt_method_db.keys()) 89 | self.ui.file_loader_btn.clicked.connect(self.__load_file_loader) 90 | self.ui.file_save_btn.clicked.connect(self.__save_files) 91 | self.ui.refresh_btn.clicked.connect(self.__refresh) 92 | self.ui.undo_btn.clicked.connect(self.__undo_modify) 93 | self.ui.mod_btn.clicked.connect(self.__modify) 94 | self.ui.src_chr_cbox.currentTextChanged.connect(self.__add_src_blks) 95 | self.ui.tgt_chr_cbox.currentTextChanged.connect(self.__add_tgt_blks) 96 | self.ui.src_blk_cbox.currentTextChanged.connect(self.__add_blk_lst) 97 | 98 | # Functions below are used for loading data and generating figure 99 | def __load_file_loader(self): 100 | file_loader = file_loader_dialog.FileLoaderDialog(self) 101 | self.__notify_with_title("Select files") 102 | file_loader.signal_path.connect(self.__get_file_path) 103 | file_loader.setModal(True) 104 | file_loader.show() 105 | 106 | def __get_file_path(self, content): 107 | if content: 108 | self.qry_bed_file, self.ref_bed_file, self.anchors_file, self.qry_agp_file = content 109 | if path.isfile(self.qry_bed_file) and path.isfile(self.ref_bed_file) and \ 110 | path.isfile(self.anchors_file) and path.isfile(self.qry_agp_file): 111 | self.__notify_with_title("Loading files") 112 | 113 | if self.__load_file(): 114 | self.__add_options() 115 | status = self.__show_pic() 116 | if status == -1: 117 | return 118 | self.__notify_with_title("Files loaded") 119 | else: 120 | QMessageBox.critical(self, 'Error', 'Cannot load files, please check input files!') 121 | self.__notify_with_title("Files load failed") 122 | return 123 | self.__enable_controls() 124 | else: 125 | self.__notify_with_title() 126 | else: 127 | self.__notify_with_title() 128 | 129 | def __load_file(self): 130 | if '/' in self.qry_bed_file: 131 | sep = '/' 132 | else: 133 | sep = '\\' 134 | self.qry_name = self.qry_bed_file.split(sep)[-1].split('.')[0] 135 | self.ref_name = self.ref_bed_file.split(sep)[-1].split('.')[0] 136 | try: 137 | self.qry_chr_list, self.qry_bed_db = self.reader.read_bed(self.qry_bed_file) 138 | if not self.qry_bed_db: 139 | return False 140 | 141 | _, self.ref_bed_db = self.reader.read_bed(self.ref_bed_file) 142 | if not self.ref_bed_db: 143 | return False 144 | 145 | self.gene_pairs = self.reader.read_anchors(self.anchors_file) 146 | if not self.gene_pairs: 147 | return False 148 | 149 | self.qry_agp_db = self.reader.read_agp(self.qry_agp_file) 150 | if not self.qry_agp_db: 151 | return False 152 | except IndexError: 153 | return False 154 | return True 155 | 156 | def __save_files(self): 157 | self.__notify_with_title("Saving files") 158 | folder_path = QFileDialog.getExistingDirectory(self, "Select folder") 159 | if folder_path and path.isdir(folder_path): 160 | self.__notify_with_title("Saving tour files") 161 | for chrn in self.qry_agp_db: 162 | tour_file = path.join(folder_path, '%s.tour' % chrn) 163 | with open(tour_file, 'w') as fout: 164 | tour_list = [] 165 | for _, _, ctg, _, direct in self.qry_agp_db[chrn]: 166 | tour_list.append("%s%s" % (ctg, direct)) 167 | fout.write("%s" % ' '.join(tour_list)) 168 | 169 | self.__notify_with_title("Saving figure") 170 | fig_file = path.join(folder_path, "%s.%s.pdf" % (self.qry_name, self.ref_name)) 171 | self.mpl_vis.figure_content.plt.savefig(fig_file, bbox_inches='tight') 172 | 173 | self.__notify_with_title("Saving blocks") 174 | block_file = path.join(folder_path, "contig_blocks.txt") 175 | with open(block_file, 'w') as fout: 176 | for chrn in sorted(self.mpl_vis.block_list_db): 177 | for idx in self.mpl_vis.block_list_db[chrn]: 178 | idx = int(idx) - 1 179 | fout.write(">%s_Block_%d\n%s\n" % (chrn, idx + 1, ' '.join(self.mpl_vis.block_detail[idx]))) 180 | 181 | QMessageBox.information(self, "Save files", "Tour files saved.") 182 | self.__notify_with_title("Success") 183 | else: 184 | self.__notify_with_title("Nothing saved") 185 | 186 | def __show_pic(self): 187 | QCoreApplication.processEvents(QEventLoop.AllEvents) 188 | try: 189 | self.__notify_with_title("Drawing") 190 | blk_loc = locator.Locator() 191 | blk_loc.convert_anchors(self.qry_bed_db, self.ref_bed_db, self.gene_pairs) 192 | if self.ui.resolution_text.text(): 193 | resolution = int(float(self.ui.resolution_text.text())) 194 | else: 195 | resolution = 20 196 | if resolution == 0 or resolution == 20: 197 | resolution = 20 198 | self.ui.resolution_text.setText("20") 199 | blk_loc.get_break_blocks(resolution) 200 | 201 | self.mpl_vis.gen_figure(blk_loc.links, blk_loc.block_db, self.qry_agp_db, resolution, 202 | self.qry_name, self.ref_name) 203 | 204 | self.block_regions = self.mpl_vis.block_regions 205 | self.block_list_db = self.mpl_vis.block_list_db 206 | self.block_detail = self.mpl_vis.block_detail 207 | 208 | self.ui.src_blk_cbox.clear() 209 | src_chr = self.ui.src_chr_cbox.currentText() 210 | if src_chr in self.block_list_db: 211 | self.ui.src_blk_cbox.addItems(self.block_list_db[src_chr]) 212 | 213 | self.ui.blk_lst.clear() 214 | self.ui.blk_lst.addItems(self.block_detail[0]) 215 | 216 | self.ui.tgt_blk_cbox.clear() 217 | if self.ui.tgt_chr_cbox.currentText() in self.block_list_db: 218 | self.ui.tgt_blk_cbox.addItems(self.block_list_db[self.ui.tgt_chr_cbox.currentText()]) 219 | if not self.graph_scene: 220 | self.graph_scene = custom_control.ControlGraphicsScene() 221 | self.graph_scene.addWidget(self.mpl_vis.figure_content) 222 | self.ui.plot_viewer.setScene(self.graph_scene) 223 | self.ui.plot_viewer.show() 224 | else: 225 | self.mpl_vis.figure_content.draw() 226 | QCoreApplication.processEvents(QEventLoop.AllEvents) 227 | self.__notify_with_title("Success") 228 | except Exception as e: 229 | QMessageBox.critical(self, "Show picture failed", format_exc()) 230 | self.__notify_with_title("Draw failed by %s" % repr(e)) 231 | return -1 232 | return 0 233 | 234 | # Functions below are used for controlling UI 235 | def __enable_controls(self): 236 | self.ui.file_save_btn.setEnabled(True) 237 | self.ui.blk_lst.setEnabled(True) 238 | self.ui.undo_btn.setEnabled(True) 239 | self.ui.mod_btn.setEnabled(True) 240 | self.ui.refresh_btn.setEnabled(True) 241 | self.ui.src_chr_cbox.setEnabled(True) 242 | self.ui.src_blk_cbox.setEnabled(True) 243 | self.ui.tgt_chr_cbox.setEnabled(True) 244 | self.ui.tgt_blk_cbox.setEnabled(True) 245 | self.ui.method_cbox.setEnabled(True) 246 | self.ui.rev_chk.setEnabled(True) 247 | self.ui.plot_viewer.setEnabled(True) 248 | 249 | def __enable_buttons(self): 250 | self.ui.mod_btn.setEnabled(True) 251 | self.ui.undo_btn.setEnabled(True) 252 | self.ui.refresh_btn.setEnabled(True) 253 | QCoreApplication.processEvents(QEventLoop.AllEvents) 254 | 255 | def __disable_buttons(self): 256 | self.ui.mod_btn.setEnabled(False) 257 | self.ui.undo_btn.setEnabled(False) 258 | self.ui.refresh_btn.setEnabled(False) 259 | QCoreApplication.processEvents(QEventLoop.AllEvents) 260 | 261 | def __add_options(self): 262 | self.ui.src_chr_cbox.addItems(self.qry_chr_list) 263 | self.ui.tgt_chr_cbox.addItems(self.qry_chr_list) 264 | 265 | def __add_src_blks(self, value): 266 | self.ui.src_blk_cbox.clear() 267 | if self.block_list_db and value in self.block_list_db: 268 | self.ui.src_blk_cbox.addItems(self.block_list_db[value]) 269 | 270 | def __add_tgt_blks(self, value): 271 | self.ui.tgt_blk_cbox.clear() 272 | if self.block_list_db and value in self.block_list_db: 273 | self.ui.tgt_blk_cbox.addItems(self.block_list_db[value]) 274 | 275 | def __add_blk_lst(self, value): 276 | self.ui.blk_lst.clear() 277 | if value: 278 | self.ui.blk_lst.addItems(self.block_detail[int(value) - 1]) 279 | 280 | def __notify_with_title(self, info=""): 281 | if info: 282 | self.setWindowTitle("CATG - %s" % info) 283 | else: 284 | self.setWindowTitle("CATG") 285 | 286 | def closeEvent(self, event): 287 | sys.exit(0) 288 | 289 | # Functions below are used for adjusting collinearity blocks 290 | def __modify(self): 291 | 292 | self.ui.mod_btn.setText("Modifying...") 293 | self.__disable_buttons() 294 | args = OptArgs() 295 | 296 | args.src_chr = self.ui.src_chr_cbox.currentText() 297 | args.src_blk = int(self.ui.src_blk_cbox.currentText()) - 1 298 | args.tgt_chr = self.ui.tgt_chr_cbox.currentText() 299 | args.tgt_blk = int(self.ui.tgt_blk_cbox.currentText()) - 1 300 | args.is_rev = self.ui.rev_chk.isChecked() 301 | opt = self.ui.method_cbox.currentText() 302 | try: 303 | if opt in self.opt_method_db: 304 | self.__notify_with_title(self.opt_method_info[opt]) 305 | 306 | self.last_agp_db = deepcopy(self.qry_agp_db) 307 | self.last_bed_db = deepcopy(self.qry_bed_db) 308 | self.opt_method_db[opt](args) 309 | self.__show_pic() 310 | self.ui.mod_btn.setText("Modify") 311 | self.__enable_buttons() 312 | self.__notify_with_title("Success") 313 | except Exception as e: 314 | QMessageBox.critical(self, "Modify failed", format_exc()) 315 | self.__notify_with_title(self.opt_method_info[opt] + " Failed with: %s" % repr(e)) 316 | return 317 | 318 | def __undo_modify(self): 319 | self.ui.undo_btn.setText("Restoring") 320 | self.__disable_buttons() 321 | if self.last_agp_db: 322 | self.__notify_with_title("Restoring last status") 323 | tmp_db = deepcopy(self.qry_agp_db) 324 | self.qry_agp_db = deepcopy(self.last_agp_db) 325 | self.last_agp_db = deepcopy(tmp_db) 326 | tmp_db = deepcopy(self.qry_bed_db) 327 | self.qry_bed_db = deepcopy(self.last_bed_db) 328 | self.last_bed_db = deepcopy(tmp_db) 329 | del tmp_db 330 | self.__show_pic() 331 | self.__enable_buttons() 332 | self.ui.undo_btn.setText("Undo") 333 | self.__notify_with_title("Success") 334 | else: 335 | self.__notify_with_title("Unable restore") 336 | 337 | def __refresh(self): 338 | self.ui.refresh_btn.setText("Refreshing") 339 | self.__disable_buttons() 340 | self.__show_pic() 341 | self.__enable_buttons() 342 | self.ui.refresh_btn.setText("Refresh") 343 | 344 | def __rev_chr(self, args): 345 | if not args.is_rev: 346 | return 347 | tmp_dict = deepcopy(self.qry_agp_db) 348 | tmp_dict[args.src_chr] = self.corrector.reverse_chr(tmp_dict[args.src_chr]) 349 | self.qry_bed_db = self.corrector.trans_anno(self.qry_agp_db, tmp_dict, self.qry_bed_db) 350 | self.qry_agp_db = deepcopy(tmp_dict) 351 | del tmp_dict 352 | 353 | def __rev_blk(self, args): 354 | if not args.is_rev: 355 | return 356 | tmp_dict = deepcopy(self.qry_agp_db) 357 | tmp_dict[args.src_chr] = self.corrector.reverse_block(tmp_dict[args.src_chr], self.block_regions[args.src_blk]) 358 | self.qry_bed_db = self.corrector.trans_anno(self.qry_agp_db, tmp_dict, self.qry_bed_db) 359 | self.qry_agp_db = deepcopy(tmp_dict) 360 | del tmp_dict 361 | 362 | def __ins_head(self, args): 363 | tmp_dict = deepcopy(self.qry_agp_db) 364 | tmp_dict[args.src_chr], extract_agp_list = self.corrector.split_block(tmp_dict[args.src_chr], 365 | self.block_regions[args.src_blk]) 366 | if args.is_rev: 367 | extract_agp_list = self.corrector.reverse_chr(extract_agp_list) 368 | 369 | tmp_dict[args.tgt_chr] = self.corrector.ins_term(tmp_dict[args.tgt_chr], extract_agp_list) 370 | self.qry_bed_db = self.corrector.trans_anno(self.qry_agp_db, tmp_dict, self.qry_bed_db) 371 | self.qry_agp_db = deepcopy(tmp_dict) 372 | del tmp_dict 373 | 374 | def __ins_tail(self, args): 375 | tmp_dict = deepcopy(self.qry_agp_db) 376 | tmp_dict[args.src_chr], extract_agp_list = self.corrector.split_block(tmp_dict[args.src_chr], 377 | self.block_regions[args.src_blk]) 378 | if args.is_rev: 379 | extract_agp_list = self.corrector.reverse_chr(extract_agp_list) 380 | 381 | tmp_dict[args.tgt_chr] = self.corrector.ins_term(tmp_dict[args.tgt_chr], extract_agp_list, False) 382 | self.qry_bed_db = self.corrector.trans_anno(self.qry_agp_db, tmp_dict, self.qry_bed_db) 383 | self.qry_agp_db = deepcopy(tmp_dict) 384 | del tmp_dict 385 | 386 | def __ins_front(self, args): 387 | tmp_dict = deepcopy(self.qry_agp_db) 388 | tmp_dict[args.src_chr], extract_agp_list = self.corrector.split_block(tmp_dict[args.src_chr], 389 | self.block_regions[args.src_blk]) 390 | if args.is_rev: 391 | extract_agp_list = self.corrector.reverse_chr(extract_agp_list) 392 | 393 | tmp_dict[args.tgt_chr] = self.corrector.ins_pos(tmp_dict[args.tgt_chr], extract_agp_list, 394 | self.block_regions[args.tgt_blk]) 395 | self.qry_bed_db = self.corrector.trans_anno(self.qry_agp_db, tmp_dict, self.qry_bed_db) 396 | self.qry_agp_db = deepcopy(tmp_dict) 397 | del tmp_dict 398 | 399 | def __ins_back(self, args): 400 | tmp_dict = deepcopy(self.qry_agp_db) 401 | tmp_dict[args.src_chr], extract_agp_list = self.corrector.split_block(tmp_dict[args.src_chr], 402 | self.block_regions[args.src_blk]) 403 | if args.is_rev: 404 | extract_agp_list = self.corrector.reverse_chr(extract_agp_list) 405 | 406 | tmp_dict[args.tgt_chr] = self.corrector.ins_pos(tmp_dict[args.tgt_chr], extract_agp_list, 407 | self.block_regions[args.tgt_blk], 408 | False) 409 | self.qry_bed_db = self.corrector.trans_anno(self.qry_agp_db, tmp_dict, self.qry_bed_db) 410 | self.qry_agp_db = deepcopy(tmp_dict) 411 | del tmp_dict 412 | 413 | def __swp_chr(self, args): 414 | if args.src_chr != args.tgt_chr: 415 | tmp_dict = deepcopy(self.qry_agp_db) 416 | tmp_list = deepcopy(tmp_dict[args.src_chr]) 417 | tmp_dict[args.src_chr] = deepcopy(tmp_dict[args.tgt_chr]) 418 | tmp_dict[args.tgt_chr] = deepcopy(tmp_list) 419 | 420 | self.qry_bed_db = self.corrector.trans_anno(self.qry_agp_db, tmp_dict, self.qry_bed_db) 421 | self.qry_agp_db = deepcopy(tmp_dict) 422 | del tmp_dict, tmp_list 423 | 424 | def __swp_blk(self, args): 425 | if args.src_blk != args.tgt_blk: 426 | if args.src_chr != args.tgt_chr: 427 | tmp_dict = deepcopy(self.qry_agp_db) 428 | tmp_dict[args.tgt_chr], tmp_dict[args.src_chr] = self.corrector.swap_blk_diff_chr( 429 | tmp_dict[args.src_chr], 430 | self.block_regions[ 431 | args.src_blk], 432 | tmp_dict[args.tgt_chr], 433 | self.block_regions[ 434 | args.tgt_blk]) 435 | 436 | self.qry_bed_db = self.corrector.trans_anno(self.qry_agp_db, tmp_dict, self.qry_bed_db) 437 | self.qry_agp_db = deepcopy(tmp_dict) 438 | 439 | del tmp_dict 440 | else: 441 | tmp_dict = deepcopy(self.qry_agp_db) 442 | tmp_dict[args.src_chr] = self.corrector.swap_blk_single_chr(tmp_dict[args.src_chr], 443 | self.block_regions[args.src_blk], 444 | self.block_regions[args.tgt_blk]) 445 | if not tmp_dict[args.src_chr]: 446 | return 447 | self.qry_bed_db = self.corrector.trans_anno(self.qry_agp_db, tmp_dict, self.qry_bed_db) 448 | self.qry_agp_db = deepcopy(tmp_dict) 449 | 450 | del tmp_dict 451 | 452 | def __remove_blk(self, args): 453 | tmp_dict = deepcopy(self.qry_agp_db) 454 | tmp_dict[args.src_chr] = self.corrector.remove_blk(tmp_dict[args.src_chr], self.block_regions[args.src_blk]) 455 | self.qry_bed_db = self.corrector.trans_anno(self.qry_agp_db, tmp_dict, self.qry_bed_db) 456 | self.qry_agp_db = deepcopy(tmp_dict) 457 | 458 | del tmp_dict 459 | -------------------------------------------------------------------------------- /coll_asm_corr_gui/main/file_loader_dialog.py: -------------------------------------------------------------------------------- 1 | from PySide6.QtWidgets import QDialog, QFileDialog, QDialogButtonBox 2 | from PySide6.QtCore import Signal 3 | from coll_asm_corr_gui.ui.ui_file_loader_dialog import Ui_FileLoaderDialog 4 | 5 | 6 | class FileLoaderDialog(QDialog): 7 | 8 | signal_path = Signal(list) 9 | 10 | def __init__(self, parent=None): 11 | super(FileLoaderDialog, self).__init__(parent) 12 | self.qry_bed_file = "" 13 | self.ref_bed_file = "" 14 | self.anchors_file = "" 15 | self.qry_agp_file = "" 16 | self.ui = None 17 | self.init_ui() 18 | 19 | def init_ui(self): 20 | self.ui = Ui_FileLoaderDialog() 21 | self.ui.setupUi(self) 22 | self.ui.qry_bed_btn.clicked.connect(self.load_qry_bed) 23 | self.ui.ref_bed_btn.clicked.connect(self.load_ref_bed) 24 | self.ui.anchors_btn.clicked.connect(self.load_anchors) 25 | self.ui.qry_agp_btn.clicked.connect(self.load_qry_agp) 26 | self.ui.check_btn.button(QDialogButtonBox.Ok).clicked.connect(self.send_path) 27 | self.ui.check_btn.button(QDialogButtonBox.Cancel).clicked.connect(self.send_cancel) 28 | 29 | def load_qry_bed(self): 30 | self.qry_bed_file = QFileDialog.getOpenFileName(self, "Select query bed file", 31 | filter="bed files(*.bed);;all files(*.*)")[0] 32 | self.ui.qry_bed_text.setText(self.qry_bed_file) 33 | 34 | def load_ref_bed(self): 35 | self.ref_bed_file = QFileDialog.getOpenFileName(self, "Select reference bed file", 36 | filter="bed files(*.bed);;all files(*.*)")[0] 37 | self.ui.ref_bed_text.setText(self.ref_bed_file) 38 | 39 | def load_anchors(self): 40 | self.anchors_file = QFileDialog.getOpenFileName(self, "Select anchors file", 41 | filter="anchors files(*.anchors);;all files(*.*)")[0] 42 | self.ui.anchors_text.setText(self.anchors_file) 43 | 44 | def load_qry_agp(self): 45 | self.qry_agp_file = QFileDialog.getOpenFileName(self, "Select query agp file", 46 | filter="agp files(*.agp);;all files(*.*)")[0] 47 | self.ui.qry_agp_text.setText(self.qry_agp_file) 48 | 49 | def send_path(self): 50 | self.qry_bed_file = self.ui.qry_bed_text.text() 51 | self.ref_bed_file = self.ui.ref_bed_text.text() 52 | self.anchors_file = self.ui.anchors_text.text() 53 | self.qry_agp_file = self.ui.qry_agp_text.text() 54 | content = [self.qry_bed_file, self.ref_bed_file, self.anchors_file, self.qry_agp_file] 55 | self.signal_path.emit(content) 56 | 57 | def send_cancel(self): 58 | self.signal_path.emit(None) 59 | 60 | def closeEvent(self, event): 61 | self.signal_path.emit(None) -------------------------------------------------------------------------------- /coll_asm_corr_gui/resources/CATG.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sc-zhang/CATG/606d7f23fe713f445e2af61d3766958bc2dfa072/coll_asm_corr_gui/resources/CATG.icns -------------------------------------------------------------------------------- /coll_asm_corr_gui/resources/CATG.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sc-zhang/CATG/606d7f23fe713f445e2af61d3766958bc2dfa072/coll_asm_corr_gui/resources/CATG.ico -------------------------------------------------------------------------------- /coll_asm_corr_gui/resources/CATG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sc-zhang/CATG/606d7f23fe713f445e2af61d3766958bc2dfa072/coll_asm_corr_gui/resources/CATG.png -------------------------------------------------------------------------------- /coll_asm_corr_gui/resources/assembly_corrector_main.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | AssemblyCorrectorMain 4 | 5 | 6 | 7 | 0 8 | 0 9 | 1936 10 | 1147 11 | 12 | 13 | 14 | CATG 15 | 16 | 17 | 18 | 19 | 20 | false 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 0 29 | 0 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 12 38 | 39 | 40 | 41 | Target chromosome: 42 | 43 | 44 | 45 | 46 | 47 | 48 | false 49 | 50 | 51 | 52 | 53 | 54 | 55 | false 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 0 64 | 0 65 | 66 | 67 | 68 | 69 | 0 70 | 0 71 | 72 | 73 | 74 | QFrame::Plain 75 | 76 | 77 | 2 78 | 79 | 80 | Qt::Horizontal 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 12 89 | 90 | 91 | 92 | Source block id: 93 | 94 | 95 | 96 | 97 | 98 | 99 | QFrame::Plain 100 | 101 | 102 | 2 103 | 104 | 105 | Qt::Horizontal 106 | 107 | 108 | 109 | 110 | 111 | 112 | false 113 | 114 | 115 | 116 | 117 | 118 | 119 | false 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 0 130 | 0 131 | 132 | 133 | 134 | 135 | 16 136 | true 137 | 138 | 139 | 140 | Load files 141 | 142 | 143 | 144 | 145 | 146 | 147 | false 148 | 149 | 150 | 151 | 0 152 | 0 153 | 154 | 155 | 156 | 157 | 16 158 | true 159 | 160 | 161 | 162 | Save files 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 12 173 | 174 | 175 | 176 | Resolution: 177 | 178 | 179 | 180 | 181 | 182 | 183 | 20 184 | 185 | 186 | 187 | 188 | 189 | 190 | false 191 | 192 | 193 | Reverse 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 12 202 | true 203 | 204 | 205 | 206 | Contigs in current block: 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 12 215 | 216 | 217 | 218 | Source chromosome: 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | false 228 | 229 | 230 | 231 | 0 232 | 0 233 | 234 | 235 | 236 | 237 | 16 238 | true 239 | 240 | 241 | 242 | Undo 243 | 244 | 245 | 246 | 247 | 248 | 249 | false 250 | 251 | 252 | 253 | 0 254 | 0 255 | 256 | 257 | 258 | 259 | 16 260 | true 261 | 262 | 263 | 264 | Refresh 265 | 266 | 267 | 268 | 269 | 270 | 271 | false 272 | 273 | 274 | 275 | 0 276 | 0 277 | 278 | 279 | 280 | 281 | 16 282 | true 283 | 284 | 285 | 286 | Modify 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | QFrame::Plain 296 | 297 | 298 | 2 299 | 300 | 301 | Qt::Horizontal 302 | 303 | 304 | 305 | 306 | 307 | 308 | false 309 | 310 | 311 | 312 | 313 | 314 | 315 | false 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 12 324 | 325 | 326 | 327 | Target block id: 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 12 336 | true 337 | 338 | 339 | 340 | Operations: 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | -------------------------------------------------------------------------------- /coll_asm_corr_gui/resources/file_loader_dialog.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | FileLoaderDialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 640 10 | 250 11 | 12 | 13 | 14 | 15 | 0 16 | 0 17 | 18 | 19 | 20 | 21 | 640 22 | 250 23 | 24 | 25 | 26 | 27 | 640 28 | 250 29 | 30 | 31 | 32 | File loader 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 0 42 | 0 43 | 44 | 45 | 46 | 47 | 16777215 48 | 30 49 | 50 | 51 | 52 | Query bed file: 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 0 61 | 0 62 | 63 | 64 | 65 | true 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 0 74 | 0 75 | 76 | 77 | 78 | 79 | 16777215 80 | 30 81 | 82 | 83 | 84 | ... 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 16777215 93 | 30 94 | 95 | 96 | 97 | Reference bed file: 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 0 106 | 0 107 | 108 | 109 | 110 | true 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 0 119 | 0 120 | 121 | 122 | 123 | 124 | 16777215 125 | 30 126 | 127 | 128 | 129 | ... 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 16777215 138 | 30 139 | 140 | 141 | 142 | Anchors file: 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 0 151 | 0 152 | 153 | 154 | 155 | true 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 0 164 | 0 165 | 166 | 167 | 168 | 169 | 16777215 170 | 30 171 | 172 | 173 | 174 | ... 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 16777215 183 | 30 184 | 185 | 186 | 187 | Query AGP file: 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 0 196 | 0 197 | 198 | 199 | 200 | true 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 0 209 | 0 210 | 211 | 212 | 213 | 214 | 16777215 215 | 30 216 | 217 | 218 | 219 | ... 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | Qt::Horizontal 229 | 230 | 231 | QDialogButtonBox::Cancel|QDialogButtonBox::Ok 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | check_btn 241 | accepted() 242 | FileLoaderDialog 243 | accept() 244 | 245 | 246 | 248 247 | 254 248 | 249 | 250 | 157 251 | 274 252 | 253 | 254 | 255 | 256 | check_btn 257 | rejected() 258 | FileLoaderDialog 259 | reject() 260 | 261 | 262 | 316 263 | 260 264 | 265 | 266 | 286 267 | 274 268 | 269 | 270 | 271 | 272 | 273 | -------------------------------------------------------------------------------- /coll_asm_corr_gui/ui/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /coll_asm_corr_gui/ui/custom_control.py: -------------------------------------------------------------------------------- 1 | from PySide6.QtWidgets import QGraphicsView, QGraphicsScene, QLineEdit 2 | from PySide6.QtCore import Qt, QPoint 3 | 4 | 5 | class ControlGraphicsView(QGraphicsView): 6 | 7 | def __init__(self, parent=None): 8 | super(ControlGraphicsView, self).__init__() 9 | self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) 10 | self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) 11 | self.setDragMode(QGraphicsView.ScrollHandDrag) 12 | self.setAcceptDrops(True) 13 | self.__zoom = 5 14 | 15 | def wheelEvent(self, event): 16 | delta = event.angleDelta().y() 17 | if delta == 0: 18 | pass 19 | if delta > 0 and self.__zoom >= 10: 20 | return 21 | elif delta < 0 and self.__zoom <= 0: 22 | return 23 | else: 24 | cur_point = event.position() 25 | scene_pos = self.mapToScene(QPoint(cur_point.x(), cur_point.y())) 26 | 27 | view_width = self.viewport().width() 28 | view_height = self.viewport().height() 29 | 30 | h_scale = cur_point.x() / view_width 31 | v_scale = cur_point.y() / view_height 32 | 33 | if delta > 0: 34 | self.scale(1.25, 1.25) 35 | self.__zoom += 1 36 | elif delta < 0: 37 | self.scale(0.8, 0.8) 38 | self.__zoom -= 1 39 | view_point = self.transform().map(scene_pos) 40 | self.horizontalScrollBar().setValue(int(view_point.x() - view_width * h_scale)) 41 | self.verticalScrollBar().setValue(int(view_point.y() - view_height * v_scale)) 42 | 43 | self.update() 44 | 45 | 46 | class ControlGraphicsScene(QGraphicsScene): 47 | def __init__(self, parent=None): 48 | super(ControlGraphicsScene, self).__init__() 49 | 50 | self.__left_click = False 51 | self.__point = QPoint(0, 0) 52 | self.__start_pos = None 53 | self.__end_pos = None 54 | 55 | def mouseMoveEvent(self, e): 56 | if self.__left_click: 57 | self.__end_pos = e.pos() - self.__start_pos 58 | self.__point = self.__point + self.__end_pos 59 | self.__start_pos = e.pos() 60 | self.repaint() 61 | 62 | def mousePressEvent(self, e): 63 | if e.button() == Qt.LeftButton: 64 | self.__left_click = True 65 | self.__start_pos = e.pos() 66 | 67 | def mouseReleaseEvent(self, e): 68 | if e.button() == Qt.LeftButton: 69 | self.__left_click = False 70 | 71 | 72 | class DragLineEdit(QLineEdit): 73 | 74 | def __init__(self, parent=None): 75 | super(DragLineEdit, self).__init__(parent) 76 | 77 | def dragEnterEvent(self, event): 78 | if event.mimeData().hasUrls(): 79 | event.acceptProposedAction() 80 | else: 81 | event.ignore() 82 | 83 | def dropEvent(self, event): 84 | if event.mimeData().hasUrls(): 85 | self.setText(event.mimeData().urls()[0].toLocalFile()) 86 | event.accept() 87 | else: 88 | event.ignore() 89 | -------------------------------------------------------------------------------- /coll_asm_corr_gui/ui/ui_assembly_corrector_main.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | ################################################################################ 4 | ## Form generated from reading UI file 'assembly_adjuster_mainSfJexb.ui' 5 | ## 6 | ## Created by: Qt User Interface Compiler version 6.4.0 7 | ## 8 | ## WARNING! All changes made in this file will be lost when recompiling UI file! 9 | ################################################################################ 10 | 11 | from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale, 12 | QMetaObject, QObject, QPoint, QRect, 13 | QSize, QTime, QUrl, Qt) 14 | from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor, 15 | QFont, QFontDatabase, QGradient, QIcon, 16 | QImage, QKeySequence, QLinearGradient, QPainter, 17 | QPalette, QPixmap, QRadialGradient, QTransform) 18 | from PySide6.QtWidgets import (QApplication, QCheckBox, QComboBox, QFrame, 19 | QGraphicsView, QGridLayout, QHBoxLayout, QLabel, 20 | QLineEdit, QListWidget, QListWidgetItem, QPushButton, 21 | QSizePolicy, QWidget) 22 | from coll_asm_corr_gui.ui.custom_control import ControlGraphicsView 23 | 24 | class Ui_AssemblyCorrectorMain(object): 25 | def setupUi(self, AssemblyCorrectorMain): 26 | if not AssemblyCorrectorMain.objectName(): 27 | AssemblyCorrectorMain.setObjectName(u"AssemblyAdjusterMain") 28 | AssemblyCorrectorMain.resize(1936, 1147) 29 | self.gridLayout_2 = QGridLayout(AssemblyCorrectorMain) 30 | self.gridLayout_2.setObjectName(u"gridLayout_2") 31 | self.plot_viewer = ControlGraphicsView(AssemblyCorrectorMain) 32 | self.plot_viewer.setObjectName(u"plot_viewer") 33 | self.plot_viewer.setEnabled(False) 34 | 35 | self.gridLayout_2.addWidget(self.plot_viewer, 0, 0, 1, 1) 36 | 37 | self.Frame = QFrame(AssemblyCorrectorMain) 38 | self.Frame.setObjectName(u"Frame") 39 | sizePolicy = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) 40 | sizePolicy.setHorizontalStretch(0) 41 | sizePolicy.setVerticalStretch(0) 42 | sizePolicy.setHeightForWidth(self.Frame.sizePolicy().hasHeightForWidth()) 43 | self.Frame.setSizePolicy(sizePolicy) 44 | self.gridLayout = QGridLayout(self.Frame) 45 | self.gridLayout.setObjectName(u"gridLayout") 46 | self.label_2 = QLabel(self.Frame) 47 | self.label_2.setObjectName(u"label_2") 48 | font = QFont() 49 | font.setPointSize(12) 50 | self.label_2.setFont(font) 51 | 52 | self.gridLayout.addWidget(self.label_2, 9, 0, 1, 1) 53 | 54 | self.blk_lst = QListWidget(self.Frame) 55 | self.blk_lst.setObjectName(u"blk_lst") 56 | self.blk_lst.setEnabled(False) 57 | 58 | self.gridLayout.addWidget(self.blk_lst, 4, 0, 1, 2) 59 | 60 | self.src_chr_cbox = QComboBox(self.Frame) 61 | self.src_chr_cbox.setObjectName(u"src_chr_cbox") 62 | self.src_chr_cbox.setEnabled(False) 63 | 64 | self.gridLayout.addWidget(self.src_chr_cbox, 7, 1, 1, 1) 65 | 66 | self.line = QFrame(self.Frame) 67 | self.line.setObjectName(u"line") 68 | sizePolicy1 = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) 69 | sizePolicy1.setHorizontalStretch(0) 70 | sizePolicy1.setVerticalStretch(0) 71 | sizePolicy1.setHeightForWidth(self.line.sizePolicy().hasHeightForWidth()) 72 | self.line.setSizePolicy(sizePolicy1) 73 | self.line.setSizeIncrement(QSize(0, 0)) 74 | self.line.setFrameShadow(QFrame.Plain) 75 | self.line.setLineWidth(2) 76 | self.line.setFrameShape(QFrame.HLine) 77 | 78 | self.gridLayout.addWidget(self.line, 2, 0, 1, 2) 79 | 80 | self.label_3 = QLabel(self.Frame) 81 | self.label_3.setObjectName(u"label_3") 82 | self.label_3.setFont(font) 83 | 84 | self.gridLayout.addWidget(self.label_3, 8, 0, 1, 1) 85 | 86 | self.line_2 = QFrame(self.Frame) 87 | self.line_2.setObjectName(u"line_2") 88 | self.line_2.setFrameShadow(QFrame.Plain) 89 | self.line_2.setLineWidth(2) 90 | self.line_2.setFrameShape(QFrame.HLine) 91 | 92 | self.gridLayout.addWidget(self.line_2, 13, 0, 1, 2) 93 | 94 | self.method_cbox = QComboBox(self.Frame) 95 | self.method_cbox.setObjectName(u"method_cbox") 96 | self.method_cbox.setEnabled(False) 97 | 98 | self.gridLayout.addWidget(self.method_cbox, 12, 1, 1, 1) 99 | 100 | self.tgt_chr_cbox = QComboBox(self.Frame) 101 | self.tgt_chr_cbox.setObjectName(u"tgt_chr_cbox") 102 | self.tgt_chr_cbox.setEnabled(False) 103 | 104 | self.gridLayout.addWidget(self.tgt_chr_cbox, 9, 1, 1, 1) 105 | 106 | self.horizontalLayout_2 = QHBoxLayout() 107 | self.horizontalLayout_2.setObjectName(u"horizontalLayout_2") 108 | self.file_loader_btn = QPushButton(self.Frame) 109 | self.file_loader_btn.setObjectName(u"file_loader_btn") 110 | sizePolicy2 = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) 111 | sizePolicy2.setHorizontalStretch(0) 112 | sizePolicy2.setVerticalStretch(0) 113 | sizePolicy2.setHeightForWidth(self.file_loader_btn.sizePolicy().hasHeightForWidth()) 114 | self.file_loader_btn.setSizePolicy(sizePolicy2) 115 | font1 = QFont() 116 | font1.setPointSize(16) 117 | font1.setBold(True) 118 | self.file_loader_btn.setFont(font1) 119 | 120 | self.horizontalLayout_2.addWidget(self.file_loader_btn) 121 | 122 | self.file_save_btn = QPushButton(self.Frame) 123 | self.file_save_btn.setObjectName(u"file_save_btn") 124 | self.file_save_btn.setEnabled(False) 125 | sizePolicy2.setHeightForWidth(self.file_save_btn.sizePolicy().hasHeightForWidth()) 126 | self.file_save_btn.setSizePolicy(sizePolicy2) 127 | self.file_save_btn.setFont(font1) 128 | 129 | self.horizontalLayout_2.addWidget(self.file_save_btn) 130 | 131 | 132 | self.gridLayout.addLayout(self.horizontalLayout_2, 1, 0, 1, 2) 133 | 134 | self.label_5 = QLabel(self.Frame) 135 | self.label_5.setObjectName(u"label_5") 136 | self.label_5.setFont(font) 137 | 138 | self.gridLayout.addWidget(self.label_5, 11, 0, 1, 1) 139 | 140 | self.resolution_text = QLineEdit(self.Frame) 141 | self.resolution_text.setObjectName(u"resolution_text") 142 | 143 | self.gridLayout.addWidget(self.resolution_text, 11, 1, 1, 1) 144 | 145 | self.rev_chk = QCheckBox(self.Frame) 146 | self.rev_chk.setObjectName(u"rev_chk") 147 | self.rev_chk.setEnabled(False) 148 | 149 | self.gridLayout.addWidget(self.rev_chk, 12, 0, 1, 1) 150 | 151 | self.label_6 = QLabel(self.Frame) 152 | self.label_6.setObjectName(u"label_6") 153 | font2 = QFont() 154 | font2.setPointSize(12) 155 | font2.setBold(True) 156 | self.label_6.setFont(font2) 157 | 158 | self.gridLayout.addWidget(self.label_6, 3, 0, 1, 1) 159 | 160 | self.label = QLabel(self.Frame) 161 | self.label.setObjectName(u"label") 162 | self.label.setFont(font) 163 | 164 | self.gridLayout.addWidget(self.label, 7, 0, 1, 1) 165 | 166 | self.horizontalLayout = QHBoxLayout() 167 | self.horizontalLayout.setObjectName(u"horizontalLayout") 168 | self.undo_btn = QPushButton(self.Frame) 169 | self.undo_btn.setObjectName(u"undo_btn") 170 | self.undo_btn.setEnabled(False) 171 | sizePolicy2.setHeightForWidth(self.undo_btn.sizePolicy().hasHeightForWidth()) 172 | self.undo_btn.setSizePolicy(sizePolicy2) 173 | self.undo_btn.setFont(font1) 174 | 175 | self.horizontalLayout.addWidget(self.undo_btn) 176 | 177 | self.refresh_btn = QPushButton(self.Frame) 178 | self.refresh_btn.setObjectName(u"refresh_btn") 179 | self.refresh_btn.setEnabled(False) 180 | sizePolicy2.setHeightForWidth(self.refresh_btn.sizePolicy().hasHeightForWidth()) 181 | self.refresh_btn.setSizePolicy(sizePolicy2) 182 | self.refresh_btn.setFont(font1) 183 | 184 | self.horizontalLayout.addWidget(self.refresh_btn) 185 | 186 | self.mod_btn = QPushButton(self.Frame) 187 | self.mod_btn.setObjectName(u"mod_btn") 188 | self.mod_btn.setEnabled(False) 189 | sizePolicy2.setHeightForWidth(self.mod_btn.sizePolicy().hasHeightForWidth()) 190 | self.mod_btn.setSizePolicy(sizePolicy2) 191 | self.mod_btn.setFont(font1) 192 | 193 | self.horizontalLayout.addWidget(self.mod_btn) 194 | 195 | 196 | self.gridLayout.addLayout(self.horizontalLayout, 14, 0, 1, 2) 197 | 198 | self.line_3 = QFrame(self.Frame) 199 | self.line_3.setObjectName(u"line_3") 200 | self.line_3.setFrameShadow(QFrame.Plain) 201 | self.line_3.setLineWidth(2) 202 | self.line_3.setFrameShape(QFrame.HLine) 203 | 204 | self.gridLayout.addWidget(self.line_3, 5, 0, 1, 2) 205 | 206 | self.src_blk_cbox = QComboBox(self.Frame) 207 | self.src_blk_cbox.setObjectName(u"src_blk_cbox") 208 | self.src_blk_cbox.setEnabled(False) 209 | 210 | self.gridLayout.addWidget(self.src_blk_cbox, 8, 1, 1, 1) 211 | 212 | self.tgt_blk_cbox = QComboBox(self.Frame) 213 | self.tgt_blk_cbox.setObjectName(u"tgt_blk_cbox") 214 | self.tgt_blk_cbox.setEnabled(False) 215 | 216 | self.gridLayout.addWidget(self.tgt_blk_cbox, 10, 1, 1, 1) 217 | 218 | self.label_4 = QLabel(self.Frame) 219 | self.label_4.setObjectName(u"label_4") 220 | self.label_4.setFont(font) 221 | 222 | self.gridLayout.addWidget(self.label_4, 10, 0, 1, 1) 223 | 224 | self.label_7 = QLabel(self.Frame) 225 | self.label_7.setObjectName(u"label_7") 226 | self.label_7.setFont(font2) 227 | 228 | self.gridLayout.addWidget(self.label_7, 6, 0, 1, 1) 229 | 230 | 231 | self.gridLayout_2.addWidget(self.Frame, 0, 1, 1, 1) 232 | 233 | 234 | self.retranslateUi(AssemblyCorrectorMain) 235 | 236 | QMetaObject.connectSlotsByName(AssemblyCorrectorMain) 237 | # setupUi 238 | 239 | def retranslateUi(self, AssemblyAdjusterMain): 240 | AssemblyAdjusterMain.setWindowTitle(QCoreApplication.translate("AssemblyAdjusterMain", u"CATG", None)) 241 | self.label_2.setText(QCoreApplication.translate("AssemblyAdjusterMain", u"Target chromosome:", None)) 242 | self.label_3.setText(QCoreApplication.translate("AssemblyAdjusterMain", u"Source block id:", None)) 243 | self.file_loader_btn.setText(QCoreApplication.translate("AssemblyAdjusterMain", u"Load files", None)) 244 | self.file_save_btn.setText(QCoreApplication.translate("AssemblyAdjusterMain", u"Save files", None)) 245 | self.label_5.setText(QCoreApplication.translate("AssemblyAdjusterMain", u"Resolution:", None)) 246 | self.resolution_text.setText(QCoreApplication.translate("AssemblyAdjusterMain", u"20", None)) 247 | self.rev_chk.setText(QCoreApplication.translate("AssemblyAdjusterMain", u"Reverse", None)) 248 | self.label_6.setText(QCoreApplication.translate("AssemblyAdjusterMain", u"Contigs in current block:", None)) 249 | self.label.setText(QCoreApplication.translate("AssemblyAdjusterMain", u"Source chromosome:", None)) 250 | self.undo_btn.setText(QCoreApplication.translate("AssemblyAdjusterMain", u"Undo", None)) 251 | self.refresh_btn.setText(QCoreApplication.translate("AssemblyAdjusterMain", u"Refresh", None)) 252 | self.mod_btn.setText(QCoreApplication.translate("AssemblyAdjusterMain", u"Modify", None)) 253 | self.label_4.setText(QCoreApplication.translate("AssemblyAdjusterMain", u"Target block id:", None)) 254 | self.label_7.setText(QCoreApplication.translate("AssemblyAdjusterMain", u"Operations:", None)) 255 | # retranslateUi 256 | 257 | -------------------------------------------------------------------------------- /coll_asm_corr_gui/ui/ui_file_loader_dialog.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | ################################################################################ 4 | ## Form generated from reading UI file 'file_loader_dialogtGRscx.ui' 5 | ## 6 | ## Created by: Qt User Interface Compiler version 5.15.2 7 | ## 8 | ## WARNING! All changes made in this file will be lost when recompiling UI file! 9 | ################################################################################ 10 | 11 | from PySide6.QtCore import * 12 | from PySide6.QtGui import * 13 | from PySide6.QtWidgets import * 14 | from coll_asm_corr_gui.ui.custom_control import DragLineEdit 15 | 16 | 17 | class Ui_FileLoaderDialog(object): 18 | def setupUi(self, FileLoaderDialog): 19 | if not FileLoaderDialog.objectName(): 20 | FileLoaderDialog.setObjectName(u"FileLoaderDialog") 21 | FileLoaderDialog.resize(640, 250) 22 | sizePolicy = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) 23 | sizePolicy.setHorizontalStretch(0) 24 | sizePolicy.setVerticalStretch(0) 25 | sizePolicy.setHeightForWidth(FileLoaderDialog.sizePolicy().hasHeightForWidth()) 26 | FileLoaderDialog.setSizePolicy(sizePolicy) 27 | FileLoaderDialog.setMinimumSize(QSize(640, 250)) 28 | FileLoaderDialog.setMaximumSize(QSize(640, 250)) 29 | self.verticalLayout = QVBoxLayout(FileLoaderDialog) 30 | self.verticalLayout.setObjectName(u"verticalLayout") 31 | self.gridLayout = QGridLayout() 32 | self.gridLayout.setObjectName(u"gridLayout") 33 | self.label = QLabel(FileLoaderDialog) 34 | self.label.setObjectName(u"label") 35 | sizePolicy1 = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Minimum) 36 | sizePolicy1.setHorizontalStretch(0) 37 | sizePolicy1.setVerticalStretch(0) 38 | sizePolicy1.setHeightForWidth(self.label.sizePolicy().hasHeightForWidth()) 39 | self.label.setSizePolicy(sizePolicy1) 40 | self.label.setMaximumSize(QSize(16777215, 30)) 41 | 42 | self.gridLayout.addWidget(self.label, 0, 0, 1, 1) 43 | 44 | self.qry_bed_text = DragLineEdit(FileLoaderDialog) 45 | self.qry_bed_text.setObjectName(u"qry_bed_text") 46 | self.qry_bed_text.setMinimumSize(QSize(0, 0)) 47 | self.qry_bed_text.setDragEnabled(True) 48 | 49 | self.gridLayout.addWidget(self.qry_bed_text, 0, 1, 1, 1) 50 | 51 | self.qry_bed_btn = QPushButton(FileLoaderDialog) 52 | self.qry_bed_btn.setObjectName(u"qry_bed_btn") 53 | sizePolicy2 = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) 54 | sizePolicy2.setHorizontalStretch(0) 55 | sizePolicy2.setVerticalStretch(0) 56 | sizePolicy2.setHeightForWidth(self.qry_bed_btn.sizePolicy().hasHeightForWidth()) 57 | self.qry_bed_btn.setSizePolicy(sizePolicy2) 58 | self.qry_bed_btn.setMaximumSize(QSize(16777215, 30)) 59 | 60 | self.gridLayout.addWidget(self.qry_bed_btn, 0, 2, 1, 1) 61 | 62 | self.label_2 = QLabel(FileLoaderDialog) 63 | self.label_2.setObjectName(u"label_2") 64 | self.label_2.setMaximumSize(QSize(16777215, 30)) 65 | 66 | self.gridLayout.addWidget(self.label_2, 1, 0, 1, 1) 67 | 68 | self.ref_bed_text = DragLineEdit(FileLoaderDialog) 69 | self.ref_bed_text.setObjectName(u"ref_bed_text") 70 | self.ref_bed_text.setMinimumSize(QSize(0, 0)) 71 | self.ref_bed_text.setDragEnabled(True) 72 | 73 | self.gridLayout.addWidget(self.ref_bed_text, 1, 1, 1, 1) 74 | 75 | self.ref_bed_btn = QPushButton(FileLoaderDialog) 76 | self.ref_bed_btn.setObjectName(u"ref_bed_btn") 77 | sizePolicy2.setHeightForWidth(self.ref_bed_btn.sizePolicy().hasHeightForWidth()) 78 | self.ref_bed_btn.setSizePolicy(sizePolicy2) 79 | self.ref_bed_btn.setMaximumSize(QSize(16777215, 30)) 80 | 81 | self.gridLayout.addWidget(self.ref_bed_btn, 1, 2, 1, 1) 82 | 83 | self.label_3 = QLabel(FileLoaderDialog) 84 | self.label_3.setObjectName(u"label_3") 85 | self.label_3.setMaximumSize(QSize(16777215, 30)) 86 | 87 | self.gridLayout.addWidget(self.label_3, 2, 0, 1, 1) 88 | 89 | self.anchors_text = DragLineEdit(FileLoaderDialog) 90 | self.anchors_text.setObjectName(u"anchors_text") 91 | self.anchors_text.setMinimumSize(QSize(0, 0)) 92 | self.anchors_text.setDragEnabled(True) 93 | 94 | self.gridLayout.addWidget(self.anchors_text, 2, 1, 1, 1) 95 | 96 | self.anchors_btn = QPushButton(FileLoaderDialog) 97 | self.anchors_btn.setObjectName(u"anchors_btn") 98 | sizePolicy2.setHeightForWidth(self.anchors_btn.sizePolicy().hasHeightForWidth()) 99 | self.anchors_btn.setSizePolicy(sizePolicy2) 100 | self.anchors_btn.setMaximumSize(QSize(16777215, 30)) 101 | 102 | self.gridLayout.addWidget(self.anchors_btn, 2, 2, 1, 1) 103 | 104 | self.label_4 = QLabel(FileLoaderDialog) 105 | self.label_4.setObjectName(u"label_4") 106 | self.label_4.setMaximumSize(QSize(16777215, 30)) 107 | 108 | self.gridLayout.addWidget(self.label_4, 3, 0, 1, 1) 109 | 110 | self.qry_agp_text = DragLineEdit(FileLoaderDialog) 111 | self.qry_agp_text.setObjectName(u"qry_agp_text") 112 | self.qry_agp_text.setMinimumSize(QSize(0, 0)) 113 | self.qry_agp_text.setDragEnabled(True) 114 | 115 | self.gridLayout.addWidget(self.qry_agp_text, 3, 1, 1, 1) 116 | 117 | self.qry_agp_btn = QPushButton(FileLoaderDialog) 118 | self.qry_agp_btn.setObjectName(u"qry_agp_btn") 119 | sizePolicy2.setHeightForWidth(self.qry_agp_btn.sizePolicy().hasHeightForWidth()) 120 | self.qry_agp_btn.setSizePolicy(sizePolicy2) 121 | self.qry_agp_btn.setMaximumSize(QSize(16777215, 30)) 122 | 123 | self.gridLayout.addWidget(self.qry_agp_btn, 3, 2, 1, 1) 124 | 125 | 126 | self.verticalLayout.addLayout(self.gridLayout) 127 | 128 | self.check_btn = QDialogButtonBox(FileLoaderDialog) 129 | self.check_btn.setObjectName(u"check_btn") 130 | self.check_btn.setOrientation(Qt.Horizontal) 131 | self.check_btn.setStandardButtons(QDialogButtonBox.Cancel|QDialogButtonBox.Ok) 132 | 133 | self.verticalLayout.addWidget(self.check_btn) 134 | 135 | 136 | self.retranslateUi(FileLoaderDialog) 137 | self.check_btn.accepted.connect(FileLoaderDialog.accept) 138 | self.check_btn.rejected.connect(FileLoaderDialog.reject) 139 | 140 | QMetaObject.connectSlotsByName(FileLoaderDialog) 141 | # setupUi 142 | 143 | def retranslateUi(self, FileLoaderDialog): 144 | FileLoaderDialog.setWindowTitle(QCoreApplication.translate("FileLoaderDialog", u"File loader", None)) 145 | self.label.setText(QCoreApplication.translate("FileLoaderDialog", u"Query bed file:", None)) 146 | self.qry_bed_btn.setText(QCoreApplication.translate("FileLoaderDialog", u"...", None)) 147 | self.label_2.setText(QCoreApplication.translate("FileLoaderDialog", u"Reference bed file:", None)) 148 | self.ref_bed_btn.setText(QCoreApplication.translate("FileLoaderDialog", u"...", None)) 149 | self.label_3.setText(QCoreApplication.translate("FileLoaderDialog", u"Anchors file:", None)) 150 | self.anchors_btn.setText(QCoreApplication.translate("FileLoaderDialog", u"...", None)) 151 | self.label_4.setText(QCoreApplication.translate("FileLoaderDialog", u"Query AGP file:", None)) 152 | self.qry_agp_btn.setText(QCoreApplication.translate("FileLoaderDialog", u"...", None)) 153 | # retranslateUi 154 | 155 | -------------------------------------------------------------------------------- /test/test_data.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sc-zhang/CATG/606d7f23fe713f445e2af61d3766958bc2dfa072/test/test_data.tar.gz --------------------------------------------------------------------------------