I would like to convert a file in gff3 format to a gtf2.2 format.

The reason why I would like to do this is: I have a set of transcripts assembled by a bunch of different software (and using RNA-seq data from different sequencing technologies) and I would like to compare each of those sets with the reference transcriptome annotation for that species (D. melanogaster). I already asked for the community advice about how to proceed on that, but to run the suggested software (TACO and cuffmerge) I need to have a GTF file containing the experimental transcripts to compare to the reference transcripts (GTF).

Up to now, I unsuccessfully tried:

  1. gffread utility in the Cufflinks package (gffread input.gff3 -T -o output.gtf): this results in an empty output.gtf file and an empty log file (used Cufflinks v.2.2.1) - I contacted the authors via their Google group but haven't heard of them yet
  2. gff3_to_gtf utility in the GenomeTools (gt) package (gt gff3_to_gtf input.gff3 -o output.gtf): the output is not created and the log file is not informative - I contacted the authors via their mailing list but haven't heard of them yet
  3. GFF3_to_GTF utility in the FML package (./gff3_to_gtf_converter.pl input.gff3 output.gtf): the output just contains a header (##gff-version 2.5) and the log is empty

The gff3 file was created as output of GMAP, and contains the transcripts as found by alignment to the reference (specifying option -f gff3_match_cdna).

[Edit: what I found out a poteriori, is that this format is not a standard gff3, thus the conversion is not trivial...]

Here is the beginning of the gff3 file I tried to convert:

$ head r9_gmap_match-cdna.gff
##gff-version   3
# Generated by GMAP version 2017-04-24 using call:  /home/aechchik/software/gmap-2017-04-24/bin/gmap.sse42 -d gmapidx -D /scratch/beegfs/monthly/aechchik/isoforms/ref/chromosomes/ -f gff3_match_cdna -n 0 -t 20 r9_2d.fasta
2L    gmapidx    cDNA_match    18442664    18443024    79    -    . ID=ae6a7818-85b5-4739-8031-e58f4462ad41_Basecall_2D_2d.path1;Name=ae6a7818-85b5-4739-8031-e58f4462ad41_Basecall_2D_2d;Target=ae6a7818-85b5-4739-8031-e58f4462ad41_Basecall_2D_2d 141 489;Gap=M13 I10 M8 D3 M8 D3 M32 D1 M4 D2 M5 D3 M34 I3 M6 D1 M7 D1 M5 D1 M30 D2 M16 I1 M8 D3 M10 D1 M5 D1 M6 I1 M8 I1 M8 D1 M33 D3 M33 I1 M28 D3 M25
3R    gmapidx    cDNA_match    15853880    15855465    96    +    . ID=dd2444cf-34d6-4cd3-87ab-0ae1f3cb1f96_Basecall_2D_2d.path1;Name=dd2444cf-34d6-4cd3-87ab-0ae1f3cb1f96_Basecall_2D_2d;Target=dd2444cf-34d6-4cd3-87ab-0ae1f3cb1f96_Basecall_2D_2d 80 1645;Gap=M46 D2 M12 D2 M157 D3 M4 D1 M50 I2 M3 I1 M12 I1 M68 I1 M66 I1 M53 D2 M47 D1 M16 D1 M35 D1 M155 D1 M28 D1 M166 D2 M47 D1 M8 D4 M69 D1 M28 D1 M5 D1 M12 D1 M202 I1 M115 D1 M61 I2 M7 D1 M7 I1 M36 D2 M41
3R    gmapidx    cDNA_match    15855529    15855742    97    +    . ID=dd2444cf-34d6-4cd3-87ab-0ae1f3cb1f96_Basecall_2D_2d.path1;Name=dd2444cf-34d6-4cd3-87ab-0ae1f3cb1f96_Basecall_2D_2d;Target=dd2444cf-34d6-4cd3-87ab-0ae1f3cb1f96_Basecall_2D_2d 1646 1856;Gap=M21 D1 M68 D1 M85 D1 M37
X    gmapidx    cDNA_match    14837810    14838142    93    -    . ID=960b50cd-945e-4c12-b9bc-367f965575bb_Basecall_2D_2d.path1;Name=960b50cd-945e-4c12-b9bc-367f965575bb_Basecall_2D_2d;Target=960b50cd-945e-4c12-b9bc-367f965575bb_Basecall_2D_2d 74 406;Gap=M13 D1 M182 I1 M14 I2 M56 I1 M30 D2 M21 D1 M13
X    gmapidx    cDNA_match    14837470    14837753    92    -    . ID=960b50cd-945e-4c12-b9bc-367f965575bb_Basecall_2D_2d.path1;Name=960b50cd-945e-4c12-b9bc-367f965575bb_Basecall_2D_2d;Target=960b50cd-945e-4c12-b9bc-367f965575bb_Basecall_2D_2d 407 688;Gap=I2 M5 I1 M64 I1 M44 D1 M9 D5 M31 D3 M23 I1 M19 I1 M25 I1 M26 D1 M19 I1 M9
  • 1
    $\begingroup$ I can't get cufflinks to work with this either. Strange. Could you explain what you need, exactly? I mean, GTF is basically GFF2. Would it be OK to just remove the attributes? What information do you need to keep? Basically, what will you use the GTF for? Do you need the gene_id and transcript_id fields and, if so, where would that information come from? It might be helpful if you came into our chat room. If you do, ping me (@terdon) to let me know. $\endgroup$
    – terdon
    Jul 14, 2017 at 14:43
  • 1
    $\begingroup$ I tried to convert this with rtracklayer in R and also failed (rtracklayer doesn't want to output proper GTF format) :( I wonder if a bit of python/perl would suffice. $\endgroup$
    – Devon Ryan
    Jul 14, 2017 at 14:56

3 Answers 3


The following bit of python code should work:

#!/usr/bin/env python
import sys

lastTranscript = [None, None, None, []]  # ID, chrom, strand, [(start, end, score), ...]

def getID(s):
    """Parse out the ID attribute"""
    s = s.split(";")
    for k in s:
        if k.startswith("ID="):
            return k[3:]
    return None

def dumpLastTranscript():
    """Print the last transcript"""
    bounds = sorted(lastTranscript[3])
    print("{}\tgmapidx\tgene\t{}\t{}\t.\t{}\t.\tgene_id \"{}\"; transcript_id \"{}\";".format(lastTranscript[1], bounds[0][0], bounds[-1][1], lastTranscript[2], lastTranscript[0], lastTranscript[0]))
    print("{}\tgmapidx\ttranscript\t{}\t{}\t.\t{}\t.\tgene_id \"{}\"; transcript_id \"{}\";".format(lastTranscript[1], bounds[0][0], bounds[-1][1], lastTranscript[2], lastTranscript[0], lastTranscript[0]))
    for start, end, score in bounds:
        print("{}\tgmapidx\texon\t{}\t{}\t{}\t{}\t.\tgene_id \"{}\"; transcript_id \"{}\";".format(lastTranscript[1], start, end, score, lastTranscript[2], lastTranscript[0], lastTranscript[0]))

def handleLine(cols):
    """Handle a single line, appending the exon bounds to the previous if relevant"""
    ID = getID(cols[8])
    assert(ID is not None)
    if lastTranscript[0] is not None and lastTranscript[0] != ID:
        lastTranscript[3] = []
    lastTranscript[0] = ID
    lastTranscript[1] = cols[0]
    lastTranscript[2] = cols[6]
    lastTranscript[3].append((int(cols[3]), int(cols[4]), cols[5]))

f = open(sys.argv[1])
for line in f:
    if line.startswith("#"):
    cols = line.strip().split("\t")


If you saved that as gff2gtf.py and made it executable, the usage would be ./gff2gtf.py foo.gff3 > foo.gtf. With the example that you provided in your post, the result is:

2L  gmapidx gene    18442664    18443024    .   -   .   gene_id "ae6a7818-85b5-4739-8031-e58f4462ad41_Basecall_2D_2d.path1"; transcript_id "ae6a7818-85b5-4739-8031-e58f4462ad41_Basecall_2D_2d.path1";
2L  gmapidx transcript  18442664    18443024    .   -   .   gene_id "ae6a7818-85b5-4739-8031-e58f4462ad41_Basecall_2D_2d.path1"; transcript_id "ae6a7818-85b5-4739-8031-e58f4462ad41_Basecall_2D_2d.path1";
2L  gmapidx exon    18442664    18443024    79  -   .   gene_id "ae6a7818-85b5-4739-8031-e58f4462ad41_Basecall_2D_2d.path1"; transcript_id "ae6a7818-85b5-4739-8031-e58f4462ad41_Basecall_2D_2d.path1";
3R  gmapidx gene    15853880    15855742    .   +   .   gene_id "dd2444cf-34d6-4cd3-87ab-0ae1f3cb1f96_Basecall_2D_2d.path1"; transcript_id "dd2444cf-34d6-4cd3-87ab-0ae1f3cb1f96_Basecall_2D_2d.path1";
3R  gmapidx transcript  15853880    15855742    .   +   .   gene_id "dd2444cf-34d6-4cd3-87ab-0ae1f3cb1f96_Basecall_2D_2d.path1"; transcript_id "dd2444cf-34d6-4cd3-87ab-0ae1f3cb1f96_Basecall_2D_2d.path1";
3R  gmapidx exon    15853880    15855465    96  +   .   gene_id "dd2444cf-34d6-4cd3-87ab-0ae1f3cb1f96_Basecall_2D_2d.path1"; transcript_id "dd2444cf-34d6-4cd3-87ab-0ae1f3cb1f96_Basecall_2D_2d.path1";
3R  gmapidx exon    15855529    15855742    97  +   .   gene_id "dd2444cf-34d6-4cd3-87ab-0ae1f3cb1f96_Basecall_2D_2d.path1"; transcript_id "dd2444cf-34d6-4cd3-87ab-0ae1f3cb1f96_Basecall_2D_2d.path1";
X   gmapidx gene    14837470    14838142    .   -   .   gene_id "960b50cd-945e-4c12-b9bc-367f965575bb_Basecall_2D_2d.path1"; transcript_id "960b50cd-945e-4c12-b9bc-367f965575bb_Basecall_2D_2d.path1";
X   gmapidx transcript  14837470    14838142    .   -   .   gene_id "960b50cd-945e-4c12-b9bc-367f965575bb_Basecall_2D_2d.path1"; transcript_id "960b50cd-945e-4c12-b9bc-367f965575bb_Basecall_2D_2d.path1";
X   gmapidx exon    14837470    14837753    92  -   .   gene_id "960b50cd-945e-4c12-b9bc-367f965575bb_Basecall_2D_2d.path1"; transcript_id "960b50cd-945e-4c12-b9bc-367f965575bb_Basecall_2D_2d.path1";
X   gmapidx exon    14837810    14838142    93  -   .   gene_id "960b50cd-945e-4c12-b9bc-367f965575bb_Basecall_2D_2d.path1"; transcript_id "960b50cd-945e-4c12-b9bc-367f965575bb_Basecall_2D_2d.path1";

That at least looks like correct gtf 2.2 format to me.

  • $\begingroup$ Close to be GTF2.2 but is not. Feature type from 3rd column should be filtered to match the GTF2.2 specification. Only 9 feature types are accepted. Gene is not as example. It became accepted in version 2.5. See here agat.readthedocs.io/en/latest/gxf.html#gtf2-2 $\endgroup$
    – juke34
    Sep 17, 2021 at 8:07
  • $\begingroup$ @juke34 As there is no coherent GFF specification, you're always going to have to tweak something or other. $\endgroup$
    – Devon Ryan
    Sep 17, 2021 at 8:10
  • $\begingroup$ There are clear specifications (I would say excepted for GFF0 and GTF1) but it is easy to not follow them ^^. Have you look at the review I made? I tried to gather all the information in one place: agat.readthedocs.io/en/latest/gxf.html $\endgroup$
    – juke34
    Sep 17, 2021 at 8:16

The GenomeTools package has lots of small applications that let you massage GFF3. It may have a converter, or it may let you tweak your existing GFF3 so that gffread works

  • $\begingroup$ Oh... I should read past the first item on the list... Try using gt gff3 to transform and output GFF3 files? $\endgroup$
    – Dan
    Jul 14, 2017 at 13:49
  • $\begingroup$ yes @Dan, I tried gt (Genome Tools package) - see point 2 of my question, and it is not working as it is. $\endgroup$
    – aechchiki
    Jul 14, 2017 at 13:53
  • $\begingroup$ Please edit your question and include an example of how the OP can use your suggested tool to do what they ask. As it stands, this is more of a comment than an answer. Please also see meta.stackexchange.com/q/225370/203101 $\endgroup$
    – terdon
    Jul 14, 2017 at 14:12
  • $\begingroup$ @terdon Do you mean the answer? $\endgroup$ Jul 14, 2017 at 20:53
  • $\begingroup$ @KamilSJaron d'oh! Yes, sorry, I was asking Dan to edit this answer, not the question. $\endgroup$
    – terdon
    Jul 17, 2017 at 0:37

I'm quite sure it will work with agat_convert_sp_gff2gtf.pl from AGAT.
See the review of the different solution I made here: https://agat.readthedocs.io/en/latest/gff_to_gtf.html

AGAT it the only one you can decide wich version of GTF you require as output.


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.