Windows中Visual studio使用Qt5的中文乱码问题

Qt的中文输入至少包括如下两类:

  1. 代码编辑器中的中文
  2. 通过文件选择框打开的中文路径

对于第一种情况,代码编辑器中的中文跟编辑器的编码有关,因此可能会出现在编辑器中能够显示的中文,但是到了Qt的UI界面中就出现了乱码,例如

1
2
3
QString status = "echo 测试 && pause";
ui.textEdit->setText(status);

文本框中出现乱码

原因: Visual Studio中对中文的默认编码和QT对中文的默认编码不一样。也就是说,我们输入了X编码方案的中文,但是在编译阶段,Qt用了Y编码方案进行解码,从而导致源码阶段的乱码。

解决方案,就是设置好编码。

1
2
3
4
5
6
//声明来源是local 8bit
QString status = QString::fromLocal8Bit("echo 测试 && pause");
ui.textEdit->setText(status);
//用unicode字面量
QString status = fromUtf16(u"echo 测试 && pause");
ui.textEdit->setText(status);

对于第二种情况,QString获取到的中文就是它默认的编码方式,因此通过文件选择框获取的文件路径,如果包含中文,它也能够在Qt的UI界面正常展示。

但是如果直接转成string,然后调用system就会出现乱码,如

1
2
3
4
//乱码
QString status = QString::fromUtf16(u"echo 测试 && pause");
string test = status.toStdString();
system(test.c_str());

cmd中的乱码

即,QString转string的时候存在编码上的问题。

解决方案, 将QString转成Loal8Bit才行,而不是直接使用toStdString,之后在构建成string,转成const char*才能避免乱码。

1
2
3
QString status = QString::fromUtf16(u"echo 测试 && pause");
string test(status.toLocal8Bit());
system(test.c_str());

参考资料

「C++名词解析」指针、常量指针、指向常量的指针、指向常量的常量指针

指针(pointer), 一个有点特殊的变量,该变量记录着它所指向的对象的内存地址。

1
2
int a = 1;
int *ptr = &a;

常量指针(const pointer), 指针本质上是个变量,因此可以用 const 进行修饰,表示该指针记录的值是一个常量。常量指针在声明时需要初始化值。

1
2
int a=1;
int *const ptr = &a;

指向常量的指针(pointer refer to const)

1
2
3

int const a = 1;
int cont *ptr = &a;

指向常量的常量指针

1
2
3

int const *const ptr

如何在Visual Studio上配置OpenCV 4.5.2用于C++项目开发

这是我第一次在号称宇宙最强的IDE Visual Studio上调用非标准库编译C++程序,中间遇到了很多的报错,让我怀疑人生。

首先,我们需要从Sourceforge下载OpenCV, 目前最新版是4.5.2

image.png

下载的exe文件双击之后会出现解压界面

解压中

解压缩后得到如下文件

解压内容

我们需要依次系统属性->高级->环境变量,找到并设置设置环境变量Path

环境变量

如果不在环境变量中设置opencv中的bin路径,会出现如下报错 【由于找不到opencv_world452d.dll, 无法继续执行代码】

error1

在Visual Studio 2019中使用OpenCV构建项目的流程如下

第一步:新建一个C++的控制台项目。

新建项目

第二步:配置项目的项目名称,例如opencv-test

image.png

创建项目之后,先将Debug 改为x64,因为后续构建的是x64的项目。

修改Debug

第三步:为了能让代码正常运行,我们需要配置这个OpenCV项目的所需的include和library路径,以及依赖的lib文件。

在菜单栏的项目(P)中选择属性(P)

项目属性

选择VC++目录(VC++ Directories)的包含目录(Include Directories),点击编辑

image.png

添加opencv的include的路径(头文件)

include

同样的操作,也用于库目录(Library Directories)的设置

library

添加library路径(路径比较深)

library路径

检查下,刚刚修改的include和library

检查状态

此外还需要增加一个包括所有模块的lib文件。选择链接器(linker)中的输入(input), 接着编辑附加依赖项

![linker(https://halo-1252249331.cos.ap-shanghai.myqcloud.com/upload/2021/05/image-75a058ac43214ee9bfb9fb18f884df75.png)

添加opencv_world452d.lib,该文件包括opencv里的所有模块(lib文件和opencv版本有关,可从lib目录中确认)。

符加依赖库

如果不设置该变量,会出现【无法解析外部符号】的报错

error2

如果写错成 opencv_world452d.dll,就会出现【 LNK1104 无法打开文件”opencv_world452d.dll”】报错(之所以写成dll,是因为之前出现的dll找不到报错,导致我以为增加的是这个文件)。

如下是测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <iostream>

using namespace cv;
using namespace std;

int main()
{
Mat image = Mat::zeros(600, 600, CV_8UC3);
putText(image, "xuzhougeng", Point(100,300), FONT_HERSHEY_COMPLEX, 2.0, Scalar(100,200,200 ), 5 );
imshow("Display Window", image);
waitKey(0);
return 0;
}

能正常出图表示能正确在项目中调用OpenCV。

参考资料:

Centos的Slurm安装笔记

因为有一些软件必须要用Slurm,所以不得不在我的主机上配置slurm。

Slurm的安装依赖于root权限

munge配置

1
2
3
4
5
wget https://github.com/dun/munge/releases/download/munge-0.5.14/munge-0.5.14.tar.xz
rpmbuild -tb --without verify munge-0.5.14.tar.xz
cd /root/rpmbuild/RPMS/x86_64
rpm -ivh munge-0.5.14-1.el7.x86_64.rpm \
munge-devel-0.5.14-1.el7.x86_64.rpm munge-libs-0.5.14-1.el7.x86_64.rpm

创建密钥

1
2
sudo -u munge /usr/sbin/mungekey -v
# mungekey: Info: Created "/etc/munge/munge.key" with 1024-bit key

生成的 munge.key 文件需要分发到所有的计算节点。

启动守护进程(daemon)

1
2
3
4
systemctl enable munge
systemctl start munge
# 检查状态
systemctl status munge

方法1: RPM安装

下载页面, https://www.schedmd.com/downloads.php

因为是CentOS7, 因此我下载的是19.05版本。 而20.11可能不再支持Python2。

1
2
3
4
5
6
wget https://download.schedmd.com/slurm/slurm-19.05.8.tar.bz2
yum install pam-devel perl-Switch -y
rpmbuild -ta slurm-19.05.8.tar.bz2
cd /root/rpmbuild/RPMS/x86_64
rpm --install slurm-*.rpm

创建用户 slurm

1
2
adduser slurm

创建配置文件(非常关键)

1
2
mkdir -p /etc/slurm
touch /etc/slurm/slurm.conf

etc中slurm.conf文件里面的配置信息来自于https://slurm.schedmd.com/configurator.html 生成,需要配置如下选项

  • SlurmctldHost: 信息来自于 hostname -f

  • NodeName: 信息来自于 hostname -f, 只不过是子节点的服务器信息,如果只有单个主机,那么同上

  • ComputeNodeAddress: 计算节点的IP地址,仅有单个节点时,信息为空

  • PartitionName: 任务分配名,改成batch

  • CPUs: 设置为空

  • CoresPerSocket: 实际的物理CPU数,例如96

  • ThreadsPerCore: 如果超线程,设置为2

  • RealMemory: 服务器内存大小,单位为Mb

  • SlurmUser: slurm要求有一个专门的用户,

  • StateSaveLocation: 一定要改成 /var/spool/slurmd, 否则会出现权限问题

最后还需要增加一行 CgroupMountpoint=/sys/fs/cgroup

启动 slurmctld, slurmd 的守护进程(deamon)

1
2
3
4
5
6
7
8
# 控制节点
systemctl enable slurmctld
systemctl start slurmctld
systemctl status slurmctld
# 计算节点
systemctl enable slurmd
systemctl start slurmd
systemctl status slurmd

方法2: 通过OpenHPC仓库

测试安装

安装结果后,我们创建一个 test.sbatch, 信息如下,用于测试

1
2
3
4
5
6
7
8
9
10
11
#!/bin/bash
#SBATCH -J test # Job name
#SBATCH -o job.%j.out # Name of stdout output file (%j expands to %jobId)
#SBATCH -N 1 # Total number of nodes requested
#SBATCH -n 2 # Total number of mpi tasks #requested
#SBATCH -t 01:30:00 # Run time (hh:mm:ss) - 1.5 hours
# Launch MPI-based executable
echo "Test output from Slurm Testjob"
NODEFILE=`generate_pbs_nodefile`
cat $NODEFILE
sleep 20

递交任务

1
2
3
sbatch ./test.sbatch 
# Submitted batch job 2

查看状态

1
squeue

如果能输出一个job.X.out 文件,说明我们的SLURM已经配置成功。

可能报错和解决方案

使用 rpm --install的时候可能会遇到如下的报错。这表示你需要安装perl的Switch模块

1
2
3
4
error: Failed dependencies:
perl(Switch) is needed by slurm-openlava-19.05.8-1.el7.x86_64
perl(Switch) is needed by slurm-torque-19.05.8-1.el7.x86_64

启动 slurmd的deamon失败

1
2
3
# systemctl start slurmd
Job for slurmd.service failed because the control process exited with error code.
See "systemctl status slurmd.service" and "journalctl -xe" for details.

按照提示运行 systemctl status slurmd.service 发现error信息如下

1
2
3
error: Node configuration differs from hardware: Procs=1:192(hw) Boards=1:1(hw) SocketsPerBoard=1:4(hw) ...e=1:2(hw)
error: cgroup namespace 'freezer' not mounted. aborting

第一个error原因是在https://slurm.schedmd.com/configurator.html 填写 “Compute Machines” 的硬件信息出现错误

第二个error原因是配置文件的默认配置表现不佳,需要做如下替换

1
echo CgroupMountpoint=/sys/fs/cgroup >> /etc/slurm/cgroup.conf

参考: https://stackoverflow.com/questions/62641323/error-cgroup-namespace-freezer-not-mounted-aborting

参考资料

配置slurm: https://slurm.schedmd.com/configurator.html

单节点slurm: http://docs.nanomatch.de/technical/SimStackRequirements/SingleNodeSlurm.html

munge配置:https://github.com/dun/munge/wiki/Installation-Guide

Slurm安装与使用: http://wiki.casjc.com/?p=378

使用Biopython调用EMBOSS的needle

最近有一个需求,计算250条序列两两之间的相似度,之后根据相似度继续进行序列过滤,即A和B如果相似度高于一个阈值,那么就把B去掉。

我最初想到的就是在Python里面调用EMBOSS的needle,准备使用Python的os或者subprocess模块,但是突然想到之前看WGDI源代码时,发现作者时通过biopython的API进行MAFFT的调用,于是就决定改用Biopython.

通过查找文档,我找到了Biopython对这部分内容的介绍,见 http://biopython.org/DIST/docs/tutorial/Tutorial.html#sec97,示例代码如下

1
2
3
4
5
6
7
from Bio.Emboss.Applications import NeedleCommandline
needle_cline = NeedleCommandline(asequence="alpha.faa",
bsequence="beta.faa",
gapopen=10, gapextend=0.5,
outfile="needle.txt")
stdout, stderr = needle_cline()
align = AlignIO.read("needle.txt", "emboss")

但是这个代码距离我最终代码还有很长的路要走,因为我需要先将序列写出到文件,然后调用命令行,然后再读取输出文件。我的问题是,能不能省掉这几次I/O操作。在Emboss文档的下面,我发现Biopython提供了两个模块,分别是Bio.pairwise2和Bio.Align.PairwiseAligner, 可以直接使用Biopython的.SeqRecord对象。按照文档描述,pairwise2和PairwiseAligner能够达到和调用EMBOSS相同的速度,经过我测试,发现两者速度的确差别不大,这说明直接在Python里面进行配对序列联配并不比先输出运行所需文件,然后读取运行生成文件这个流程快,所以经过一番折腾之后,我还是决定使用NeedleCommandline。

为了方便后续调用,我写了一个函数,函数的前3个参数是用来从SeqRecord列表中提取序列用于写出,第4个参数用来提供needle程序的路径。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
def run_needle(aseq_pos, bseq_pos, seq, needle_path, verbose=False):
"""run EMBOSS needle retrun align
"""

# get the Bio object
aseq = seq[aseq_pos]
bseq = seq[bseq_pos]

# create tempfile for write fasta and needle result
aseq_file = mkstemp(suffix=".fas")[1]
bseq_file = mkstemp(suffix=".fas")[1]

needle_file = mkstemp(suffix=".needle")[1]

# write the sequence
SeqIO.write(aseq, aseq_file, "fasta")
SeqIO.write(bseq, bseq_file, "fasta")

# build needle commnad
if needle_path is None:
needle_cmd = NeedleCommandline( asequence=aseq_file, bsequence=bseq_file,
gapopen=10, gapextend=0.5, outfile=needle_file)
else:
assert os.path.isfile(needle_path)
needle_cmd = NeedleCommandline( needle_path, asequence=aseq_file, bsequence=bseq_file,
gapopen=10, gapextend=0.5, outfile=needle_file)

# running needle_cmd
stdout, stderr = needle_cmd()
if verbose:
print(stdout + stderr, file = sys.stderr)

align = AlignIO.read(needle_file, "emboss")
# deleting the tempfile
os.unlink(aseq_file)
os.unlink(bseq_file)
os.unlink(needle_file)
return align

在写这个函数时,我在输出文件的文件名上折腾了很长时间,最初想使用原序列的ID,但ID包括一些奇奇怪怪的字符,例如(|:, 而NeedleCommandline不会自动在构建的命令中增加引号,使得运行时会出错。(你可以试试echo a(b会出现什么情况 )

后来,我使用了随机数作为文件名。这个方法在单个进程时问题不大,当时当我为了加速使用多进程时,却发现发现随机数重复了。进程A输出的文件A由于和进程B的输入文件名一样,导致被跑完的进程B删了。当然,解决方法也很简单,加上一些判断文件是否存在的ifelse语句即可。

不过,我还是选择调用tempfile标准库,使用mkstemp获取临时文件名,才解决了输出文件名的危机。

最终我就可以在循环中调用这个函数,然后将结果保存在列表中,用于后续处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
fasta = "/path/to/your/sequence.fasta"
seqs = [ fas for fas in SeqIO.parse(fasta, "fasta") ]
needle_path = "/opt/biosoft/EMBOSS-6.6.0/bin/needle"

align_res = []

for i in range(len(seqs)):
for j in range(i+1, len(seqs)):
align = run_needle(i, j, seqs , needle_path)
align_res.append(align )
# 以pickle保存python对象
outfile = open("pairwise_stats.txt", "w")
for align in align_res:
line = get_psa_stat(align)
line = "\t".join(line) + "\n"
outfile.write(line)

outfile.close()

根据我的计算,循环要运行3万多次,按照平均1s一个循环,只要500多分钟,小于9个小时,我只要在回去前运行这个程序(写完第一版大概是晚上10点),然后第二天早上过来收结果就行了。

但是,正如前文提及到的,「该程序会在多个进程运行时报错」,你也就知道我没有跑循环,而是选择了通过并行对循环进行加速。在一篇中,我将会讲一个,如何把一个简单的问题复杂化,最终又回到最初代码的故事

如何提高Github的访问速度

最近总觉得Github的访问速度变慢了,导致我的工作效率也肉眼可见的降低了,主要体现在代码数量和质量的双重降低。

为了解决这一问题,我通过网络检索找到了一个非常好的工具,叫做dev-sidecar (https://github.com/docmirror/dev-sidecar), 这个工具的好处在于,图形化界面,完美解决了windows和MacOS上的问题,但是问题也出在图形化界面上,这意味着没有配置图形界面的Linux就用不了。

但是这难不倒我们,因为我看到了这个工具的参考部分。

reference

从中, 我们可以看到一个非常重要的地址,fastgit, 这是一个对于 GitHub.com 的镜像加速器。它的使用方法及其简单,也就是将原来的github.com替换成 hub.fastgit.org即可。

以 bioconda/bioconda-recipes为例,在使用原版的GitHub时,我们的下载速度基本上维持在2MiB/s,某些时候可能不到100Kb.

1
2
3
4
5
6
7
8
9
10
$ time git clone https://github.com/bioconda/bioconda-recipes
Cloning into 'bioconda-recipes'...
remote: Enumerating objects: 64, done.
remote: Counting objects: 100% (64/64), done.
remote: Compressing objects: 100% (50/50), done.
remote: Total 276716 (delta 30), reused 31 (delta 11), pack-reused 276652
Receiving objects: 100% (276716/276716), 303.49 MiB | 3.72 MiB/s, done.
Resolving deltas: 100% (152345/152345), done.
Checking out files: 100% (17906/17906), done.
git clone https://github.com/bioconda/bioconda-recipes 46.51s user 7.04s system 53% cpu 1:39.24 total

但是使用fastgit加速之后,我的下载速度直接飙升到10Mib/s以上,峰值可以达到30Mib/s.

1
2
3
4
5
6
7
8
9
10
$ time git clone https://hub.fastgit.org//bioconda/bioconda-recipes
Cloning into 'bioconda-recipes'...
remote: Enumerating objects: 64, done.
remote: Counting objects: 100% (64/64), done.
remote: Compressing objects: 100% (50/50), done.
remote: Total 276716 (delta 30), reused 31 (delta 11), pack-reused 276652
Receiving objects: 100% (276716/276716), 303.23 MiB | 21.16 MiB/s, done.
Resolving deltas: 100% (152348/152348), done.
Checking out files: 100% (17906/17906), done.
git clone https://hub.fastgit.org//bioconda/bioconda-recipes 44.88s user 6.09s system 101% cpu 50.367 total

速度的提升可能是其次的,最重要的是原本因为网络问题的fatal error导致根本下载不了的repos,现在起码能保证能克隆到本地了,实现了从0到1的进步。

当然,如果觉得每次都需要替换URL太过麻烦,fastgit还支持直接修改Git的配置,即

1
2
git config --global url."https://hub.fastgit.org/".insteadOf "https://github.com/"
git config --global protocol.http.allow always

之后,原本需要从Github上克隆的资源都会被定向到fastgit上,不再需要手动进行修改了。

不过这一缺陷在于,部分时候镜像站点可能会出现不可用的情况,此时你从Github克隆时依旧会被改向到镜像站点,你会误以为原站点出现了问题。不过只要我们人人都去支持下这个项目,随着可用节点的增多,这一缺陷也不是问题了。

如何用WGDI进行共线性分析(三)

在上一篇教程的最后,我写道「我们能够更加直观的观察到2个peak,基本确定2个多倍化事件」。这里就引出了一个问题,为什么我们可以根据peak来推断多倍化时间?在Lynch和Conery在2000年发表在Science的论文中,他们证明了小规模基因复制的Ks分布是L型,而在L型分布背景上叠加的峰则是来自于演化历史中某个突然的大规模复制事件。例如下图a中,实线是小规模复制的L型分布(呈指数分布, exponential distribution), 最初的峰可能是近期的串联复制引起,随着时间推移基因丢失,形成一个向下的坡。另一条虚线中的峰(呈正态分布, normal distribution)则是由全基因组复制引起。而b-h是一些物种的ks分布。

注,Lynch和Conery这两人是最早一批研究真核基因组的复制基因的总体保留和丢失情况的研究者。

Kevin Vanneste et al. 2012 Molecular Biology and Evolution

也就是说,我们可以通过对Ks频率分布图的观察来判断物种在历史上发生的基因组复制事件。同时又因为WGD在Ks频率分布图中表现为正态分布,那么我们可以通过对峰进行拟合来得到WGD事件所对应的Ks值。

使用WGDI对峰进行拟合需要注意的是,它一次只能拟合一个峰,因此我们需要先通过之前Ks可视化中所用到kspeaks模块(-kp)来过滤,接着用PeaksFit(-pf)模块对峰进行拟合得到模型参数,最后用KsFigures(-kf)将拟合结果绘制到一张图上。

过滤的目的是筛选出同一个WGD事件所形成的共线性区块,这可以通过设置kspeaks模块中和过滤有关的参数来实现

  • pvalue: 共线性区块的显著性
  • tandem: 是否过滤串联重复基因
  • block_length: 共线性区块的基因对的数目
  • ks_area: 根据 ks_median 筛选给定区间的共线性区块
  • multiple: 选择homo的哪列作为筛选标准
  • homo: 根据homo,筛选给定区间内的共线性区块对

其中比较关键的是ks_area 和homo,mutiple, 前者确定ks的大致区间,后者则是更精细地筛选共线性区块。block_length可以过滤掉一些比较小的共线性区块,毕竟基因数越多,就越不可能是随机事件所产生的共线性。

通过观察之前Ks频率分布图,我们大致确定两个峰所对应的区间分别是01.25,1.252.5。

figure 1

我们分别用 wgdi -kp \? > peak1.confwgdi -kp \? > peak2.conf创建两个文件,主要修改其中的ks_area, homo都设置为-1,1 表示不筛选

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# peak1.conf
[kspeaks]
blockinfo = ath_block_information.csv
pvalue = 0.05
tandem = true
block_length = 5
ks_area = 0,1.25
multiple = 1
homo = -1,1
fontsize = 9
area = 0,2
figsize = 10,6.18
savefig = ath.ks_peak1.distri.pdf
savefile = ath.ks_peak1.distri.csv

# peak2
[kspeaks]
blockinfo = ath_block_information.csv
pvalue = 0.05
tandem = true
block_length = 5
ks_area = 1.25,2.5
multiple = 1
homo = -1,1
fontsize = 9
area = 0,2
figsize = 10,6.18
savefig = ath.ks_peak2.distri.pdf
savefile = ath.ks_peak2.distri.csv

运行 wgdi -kp peak1.confwgdi -kp peak.conf输出结果。下图中左是peak1, 右是peak2.

figure 2

得到这个图之后,我们还可以进一步绘制Ks点阵图, 使用 wgdi -bk >>peak1.confwgdi -bk >> peak2.conf增加配置信息,然后对其进行修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# peak1
[blockks]
lens1 = ath.len
lens2 = ath.len
genome1_name = Athaliana
genome2_name = Athaliana
blockinfo = ath.ks_peak1.distri.csv
pvalue = 0.05
tandem = true
tandem_length = 200
markersize = 1
area = 0,2
block_length = 5
figsize = 8,8
savefig = ath.ks_peak1_dotplot.pdf
# peak2
[blockks]
lens1 = ath.len
lens2 = ath.len
genome1_name = Athaliana
genome2_name = Athaliana
blockinfo = ath.ks_peak2.distri.csv
pvalue = 0.05
tandem = true
tandem_length = 200
markersize = 1
area = 0,2
block_length = 5
figsize = 8,8
savefig = ath.ks_peak2_dotplot.pdf

运行 wgdi -bk peak1.conf,和 wgdi -bk peak2.conf 输出结果。下图中,左边是peak1,右边是peak2.

figure 3

由于我们设置的是 homo=-1,1 因此只是根据共线性块的Ks中位数筛选不同的peak,如果需要更加精细的挑选给定Ks中位数范围内的共线性区块,可以尝试修改homo值再观察结果。

homo值表示的共线性区块中基因对的匹配情况,越接近于1,越有可能是近期WGD事件得到的基因对,越接近-1,越有可能是古老WGD事件得到的基因对。

接着,我们将用blockks步骤输出的ath.ks_peak1.distri.csv和tah.ks_peak2.distri.csv 作为peakfit模块的输入,用于拟合。

分别用 wgdi -pf \? >> peak1.confwgdi -pf \? >> peak2.conf 创建配置文件, 主要修改blockinfo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# peak1
[peaksfit]
blockinfo = ath.ks_peak1.distri.csv
mode = median
bins_number = 200
ks_area = 0,10
fontsize = 9
area = 0,3
figsize = 10,6.18
savefig = ath.ks_peak1_peaksfit.pdf

# peak2
[peaksfit]
blockinfo = ath.ks_peak2.distri.csv
mode = median
bins_number = 200
ks_area = 0,10
fontsize = 9
area = 0,3
figsize = 10,6.18
savefig = ath.ks_peak2_peaksfit.pdf

分别运行 wgdi -pf peak1.confwgdi -pf peak2.conf, 会得到拟合的图,以及拟合得到参数

peak1拟合参数

1
2
3
R-square: 0.9804748699350867
The gaussian fitting curve parameters are :
5.02360835744403 | 0.8319599832352784 | 0.10382203381206191

peak2拟合参数

1
2
3
R-square: 0.9188261142099129
The gaussian fitting curve parameters are :
2.084853812322066 | 1.8332872127128195 | 0.2506813629824027

拟合参数将会用于后续的 ksfigure 模块。

我们新建一个 all_ks.csv文件, 该文件的第一行为标题行,第二行以后为数据行。一共有4+3n列,其中第一列是样本信息,第2-3列对应线条的属性,后面都是拟合参数

1
2
,color,linewidth,linestyle,,,,,,
ath_ath,green,1,--,5.02360835744403,0.8319599832352784,0.10382203381206191,2.084853812322066,1.8332872127128195,0.2506813629824027

注意,在终端里面编辑时,需要记住linestyle后面的逗号数量是 3n个, 其中n是最大peak数,例如拟南芥有2个peak,那么就得有6个逗号。

假如,我们之前用wgdi拟合过其他物种的peak,也得到了一些拟合参数,那么添加到这个文件中,例如

1
2
3
4
5
 ,color,linewidth,linestyle,,,,,,
ath_ath,red,1,--,5.02360835744403,0.8319599832352784,0.10382203381206191,2.084853812322066,1.8332872127128195,0.2506813629824027
vvi_vvi,yellow,1,-,3.00367275,1.288717936,0.177816426
vvi_oin_gamma,orange,2,-,1.910418336,1.328469514,0.262257112
vvi_oin,green,1,-,4.948194212,0.882608858,0.10426

最后我们用 wgdi -kf >> ath.conf , 创建配置文件并进行修改

1
2
3
4
5
6
7
8
9
10
[ksfigure]
ksfit = all_ks.csv
labelfontsize = 15
legendfontsize = 15
xlabel = none
ylabel = none
title = none
area = 0,4
figsize = 10,6.18
savefig = all_ks.pdf

运行 wgdi -kf ath.conf 得到下图

figure 4

最后,对这篇教程进行总结。

  1. 我们依次使用 wgdi -kp 过滤共线性区块, wgdi -pf 对峰进行拟合, wgdi -kf 展示最后的拟合结果
  2. 共线性区块的过滤是一个精细的工作,可以尝试设置不同的homo范围观察峰的变化来确定参数是否合理

WGDI之深入理解blockinfo输出结果

blockinfo模块输出文件以csv格式进行存放,共23列,可以用EXCEL直接打开。

block info

其中16列非常容易裂解,描述如下

  1. id 即共线性的结果的唯一标识

  2. chr1,start1,end1 即参考基因组(点图的左边)的共线性范围(对应GFF1的位置)

  3. chr2,start2,end2 即参考基因组(点图的上边)的共线性范围(对应GFF2的位置)

  4. pvalue 即共线性结果评估,常常认为小于0.01的更合理些

  5. length 即共线性片段中基因对数目

  6. ks_median 即共线性片段上所有基因对ks的中位数(主要用来评判ks分布的)

  7. ks_average 即共线性片段上所有基因对ks的平均值

  8. block1,block2分别为共线性片段上基因order的位置。

  9. ks共线性片段上所有基因对的ks

  10. density1,density2 共线性片段的基因分布密集程度。值越小表示稀疏。

最后两列,class1class2会在 alignment 模块中用到,对应的是两个block分组,默认值是0表示两个block是同一组。这两列后期需要自己根据覆盖率,染色体核型等多个方面进行确定。举个例子,我们可以根据 homo1 的取值范围对class1进行赋值,例如-1-0.5 是 1,-0.5 ~ 0.5 是2,0.51是3,最后在alignment中会就会用三种颜色来展示,例如下图的1,2,3分别对应red,blue,green.

alignment

中间的homo1,homo2,homo3,homo4,homo5并非那么直观,先说结论:

  • 这里的homoN(N=1,2,3,4,5) 表示一个基因有N个最佳匹配时的取值

  • N由mutiple参数确定,对应点阵图(dotplot)中的红点

  • multiple的取值一般取1即可,表示最近一次的WGD可能是一次二倍化事件,因此每个基因只会有一个最佳匹配。如果设置为2,可能是一次3倍化,每个基因由两个最佳匹配。当然实际情况可能会更加复杂,比如说异源四倍体,或者异源六倍体,或者没有多倍化只是小规模的基因复制(small-scale gene duplication) 等情况,也会影响multiple的设置。

  • homoN会在后面过滤共线性区块时用到,一般最近的WGD事件所产生的共线性区块会比较接近1,而古老的WGD产生的共线性区块则接近-1.

接着,我们将根据源代码 blast_homoblast_position 来说明结算过程。

首先需要用到blast_homo函数,用来输出每个基因对在不同最佳匹配情况下的取值(-1,0,1)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def blast_homo(self, blast, gff1, gff2, repeat_number):
index = [group.sort_values(by=11, ascending=False)[:repeat_number].index.tolist()
for name, group in blast.groupby([0])]
blast = blast.loc[np.concatenate(
np.array([k[:repeat_number] for k in index], dtype=object)), [0, 1]]
blast = blast.assign(homo1=np.nan, homo2=np.nan,
homo3=np.nan, homo4=np.nan, homo5=np.nan)
for i in range(1, 6):
bluenum = i+5
redindex = np.concatenate(
np.array([k[:i] for k in index], dtype=object))
blueindex = np.concatenate(
np.array([k[i:bluenum] for k in index], dtype=object))
grayindex = np.concatenate(
np.array([k[bluenum:repeat_number] for k in index], dtype=object))
blast.loc[redindex, 'homo'+str(i)] = 1
blast.loc[blueindex, 'homo'+str(i)] = 0
blast.loc[grayindex, 'homo'+str(i)] = -1
return blast

for循环前的代码作用是提取每个基因BLAST后的前N个最佳结果。循环的作用基因对进行赋值,主要规则是基因对如果在点图中为红色,赋值为1,蓝色赋值为0,灰色赋值为-1。

  • homo1 对应 redindex = 0:1, bluenum = 1:6, grayindex = 6:repeat_number

  • homo2 对应redindex = 0:2, bluenum = 2:7, grayindex = 7:repeat_number

  • homo5对应redindex=0:5, bluenum=5:10, grayindex = 10:repeat_number

最终函数返回的就是每个基因对,在不同最佳匹配数下的赋值结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
                0          1  homo1  homo2  homo3  homo4  homo5
185893 AT1G01010 AT4G01550 1.0 1.0 1.0 1.0 1.0
185894 AT1G01010 AT1G02230 0.0 1.0 1.0 1.0 1.0
185899 AT1G01010 AT4G35580 -1.0 0.0 0.0 0.0 0.0
185900 AT1G01010 AT1G33060 -1.0 -1.0 0.0 0.0 0.0
185901 AT1G01010 AT3G49530 -1.0 -1.0 -1.0 0.0 0.0
185902 AT1G01010 AT5G24590 -1.0 -1.0 -1.0 -1.0 0.0
250822 AT1G01030 AT1G13260 0.0 0.0 0.0 1.0 1.0
250823 AT1G01030 AT1G68840 0.0 0.0 0.0 0.0 1.0
250825 AT1G01030 AT1G25560 0.0 0.0 0.0 0.0 0.0
250826 AT1G01030 AT3G25730 -1.0 0.0 0.0 0.0 0.0
250824 AT1G01030 AT5G06250 -1.0 -1.0 0.0 0.0 0.0

然后block_position函数, 会用 for k in block[1]的循环提取每个共线性区块中每个基因对的homo值,然后用 df = pd.DataFrame(blk_homo) homo = df.mean().values求均值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
def block_position(self, collinearity, blast, gff1, gff2, ks):
data = []
for block in collinearity:
blk_homo, blk_ks = [], []
if block[1][0][0] not in gff1.index or block[1][0][2] not in gff2.index:
continue
chr1, chr2 = gff1.loc[block[1][0][0],
'chr'], gff2.loc[block[1][0][2], 'chr']
array1, array2 = [float(i[1]) for i in block[1]], [
float(i[3]) for i in block[1]]
start1, end1 = array1[0], array1[-1]
start2, end2 = array2[0], array2[-1]
block1, block2 = [], []
## 提取block中对应基因对的homo值
for k in block[1]:
block1.append(int(float(k[1])))
block2.append(int(float(k[3])))
if k[0]+","+k[2] in ks.index:
pair_ks = ks[str(k[0])+","+str(k[2])]
blk_ks.append(pair_ks)
elif k[2]+","+k[0] in ks.index:
pair_ks = ks[str(k[2])+","+str(k[0])]
blk_ks.append(pair_ks)
else:
blk_ks.append(-1)
if k[0]+","+k[2] not in blast.index:
continue
blk_homo.append(
blast.loc[k[0]+","+k[2], ['homo'+str(i) for i in range(1, 6)]].values.tolist())
ks_arr = [k for k in blk_ks if k >= 0]
if len(ks_arr) == 0:
ks_median = -1
ks_average = -1
else:
arr_ks = [k for k in blk_ks if k >= 0]
ks_median = base.get_median(arr_ks)
ks_average = sum(arr_ks)/len(arr_ks)
# 对5列homo值求均值
df = pd.DataFrame(blk_homo)
homo = df.mean().values
if len(homo) == 0:
homo = [-1, -1, -1, -1, -1]
blkks = '_'.join([str(k) for k in blk_ks])
block1 = '_'.join([str(k) for k in block1])
block2 = '_'.join([str(k) for k in block2])
data.append([block[0], chr1, chr2, start1, end1, start2, end2, block[2], len(
block[1]), ks_median, ks_average, homo[0], homo[1], homo[2], homo[3], homo[4], block1, block2, blkks])
data = pd.DataFrame(data, columns=['id', 'chr1', 'chr2', 'start1', 'end1', 'start2', 'end2',
'pvalue', 'length', 'ks_median', 'ks_average', 'homo1', 'homo2', 'homo3',
'homo4', 'homo5', 'block1', 'block2', 'ks'])
data['density1'] = data['length'] / \
((data['end1']-data['start1']).abs()+1)
data['density2'] = data['length'] / \
((data['end2']-data['start2']).abs()+1)
return data

最终得到的homo1的homo5,是不同最佳匹配基因数下计算的值。如果共线性的点大部分为红色,那么该值接近于1;如果共线性的点大部分为蓝色,那么该值接近于0;如果共线性的点大部分为灰色,那么该值接近于-1。也就是我们可以根据最初的点图中的颜色来确定将来筛选不同WGD事件所产生共线性区块。

这也就是为什么homoN可以作为共线性片段的筛选标准。

如何用WGDI进行共线性分析(二)

Ks可视化

我们在上一篇如何用WGDI进行共线性分析(一)得到共线性分析结果和Ks值输出结果的整合表格文件后,我们就可以绘制Ks点阵图和Ks频率分布图对共线性区的Ks进行探索性分析,从而确定可能的Ks峰值,用于后续分析。

Ks点阵图

最初绘制的点图信息量很大,基本上涵盖了历史上发生的加倍事件所产生的共线性区。我们能大致的判断片段是否存在复制以及复制了多少次,至于这些片段是否来自于同一次加倍事件则不太好确定。借助于Ks信息 (Ks值可以反应一定尺度内的演化时间),我们就可以较为容易地根据点阵图上共线性区域的颜色来区分多倍化事件。

首先,创建配置文件(这次是 -bk模块,BlockKs)

1
wgdi -bk \? >> ath.conf

然后,修改配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[blockks]
lens1 = ath.len
lens2 = ath.len
genome1_name = A. thaliana
genome2_name = A. thaliana
blockinfo = ath_block_information.csv
pvalue = 0.05
tandem = true
tandem_length = 200
markersize = 1
area = 0,3
block_length = 5
figsize = 8,8
savefig = ath.ks.dotplot.pdf

blockinfo 是前面共线性分析和Ks分析的整合结果,是绘图的基础。根据下面几个标准进行过滤

  • pvalue: 共线性区的显著性, 对应blockinfo中的pvalue列
  • tandem: 是否过滤由串联基因所形成的共线性区,即点阵图中对角线部分
  • tandem_length: 如果过滤,那么评估tandem的标准就是两个区块的物理距离
  • block_length: 一个共线区的最小基因对的数量,对应blockinfo中的length列

最后运行wgdi,输出图片。

1
wgdi -bk ath.conf

输出图片中的Ks的取值范围由参数area控制。图中的每个点都是各个共线性区内的基因对的Ks值。不难发现每个共线区内的Ks的颜色都差不多,意味着Ks值波动不大,基于这个现象,我们在判定Ks峰值的时候,采用共线性区的中位数就比其他方式要准确的多。在图中,我们大致能观察到2种颜色,绿色和蓝色,对应着两次比较近的加倍事件。

Ks点阵图

Ks频率分布图

除了用点阵图展示Ks外,我们还可以绘制Ks频率的分布情况。假如一个物种在历史上发生过多倍化,那么从那个时间点增加的基因经过相同时间的演化后,基因对之间的Ks值应该相差不多,即,归属于某个Ks区间的频率会明显高于其他区间。

首先,创建配置文件(-kp, ksPeak)

1
2
wgdi -kp ? >> ath.conf

然后,修改配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
[kspeaks]
blockinfo = ath_block_information.csv
pvalue = 0.05
tandem = true
block_length = 5
ks_area = 0,10
multiple = 1
homo = -1,1
fontsize = 9
area = 0,3
figsize = 10,6.18
savefig = ath.ks_median.distri.pdf
savefile = ath.ks_median.distri.csv

这一步除了输出Ks峰图(savefig)外,还会输出一个根据输入文件( blockinfo )进行过滤后的输出文件(savefile)。过滤标准除了之前Ks点阵图提及的 tandem, pvalue, block_length 外,还多了三个参数, ks_area, multiple, homo.

  • pvalue: 共线性区的显著性, 对应blockinfo中的pvalue列,pvalue设置为0.05时会保留看着很不错的共线性片段,但是会导致古老片段的减少。

  • ks_area对应blockinfo中的ks列,该列记录了共线区所有基因对的ks值。ks_area=0,10 表示只保留ks值在0到10之间的基因对。

  • multiple和blockinfo中的homo1,homo2,homo3,home4,homo5…列有关。一般默认为1, 表示选择homo1列用于后续的过滤。如果改成multiple=2, 表示选择homo2

  • homo用于根据共线性中基因对的总体得分(取值范围为-1到1,值越高表明最佳匹配的基因对越多)对共线性区域进行过滤。当multiple=1, homo=-1,1时,表示根据homo1进行过滤,只保留取值范围在-1到1之间的共线性区。

最后运行程序

1
wgdi -kp ath.conf

Ks频率分布图

从图中,我们能够更加直观的观察到2个peak,基本确定2个多倍化事件。

既然得到了一个Ks的peak图,我们可以和另一款工具wgd的拟南芥分析结果进行对比。wgd的Ks值计算流程为,先进行所有蛋白之间的相互比对,根据基因之间的同源性进行聚类,然后构建系统发育树确定同源基因,最后调用PAML的codeml计算Ks,对应下图A的A.thaliana。如果存在参考基因组,那么根据共线性锚点(对应下图D)对Ks分布进行更新, 对应下图A的A.thaliana anchors。

wgd的分析结果

同样是拟南芥的分析,wgd的点图(上图D)信息比较杂乱,存在较多的噪声点,而WGDI的Ks点阵图能有效的反应出不同共线性区域的Ks特点。wgd的Ks分布图中的只能看到一个比较明显的峰,而WGDI的分析结果能明显的观察到两个。wgd在Ks上存在的问题很大一部分原因是它们是直接采用旁系同源基因计算ks,容易受到串联重复基因积累的影响。而wgd则是基于共线性区计算Ks,结果更加可靠,尤其是后续还可以通过不断的调整参数,来确保Ks的峰值正确判断,这也是为什么在绘制Ks频率分布图的同时还要生成一个过滤后的文件。

题外话: wgd是我在2019年学习WGD相关分析找到的一个流程工具,那个时候虽然也看了文章里面关于软件的细节介绍,但是由于对比较基因组学这一领域并不熟悉,所以也不好评判结果的可靠性。最近在学习wgdi时,一直和开发者反复讨论软件的一些参数细节,这才知道这里面的很多细节。这也警醒我,不能追求软件数量上的多,只求用软件跑出自己想要的结果发表文章,失去了科研的严谨性。

我们会在下一节介绍如何利用Ks点阵图和Ks频率分布图更可靠的拟合Ks峰值。