//-------+---------+---------+---------+---------+---------+---------+--------= // // File: sam.c // //---------- // // sam-- // Support for printing alignments in SAM format. // // SAM format is a pairwise alignment format designed for short read alignments. // A spec can be found at samtools.sourceforge.net/SAM1.pdf. //---------- //---------- // // other files // //---------- #include // standard C stuff #define true 1 #define false 0 #include // standard C string stuff #include // standard C variable argument list stuff #include "build_options.h" // build options #include "utilities.h" // utility stuff #include "dna_utilities.h" // dna/scoring stuff #include "sequences.h" // sequence stuff #include "edit_script.h" // alignment edit script stuff #include "identity_dist.h" // identity distribution "format" stuff #include "coverage_dist.h" // query coverage distribution stuff #define sam_owner // (make this the owner of its globals) #include "sam.h" // interface to this module // SAM bit-encoded flags #define BAM_FPAIRED 1 // the read is paired in sequencing, no matter whether it is mapped in a pair #define BAM_FPROPER_PAIR 2 // the read is mapped in a proper pair #define BAM_FUNMAP 4 // the read itself is unmapped; conflictive with BAM_FPROPER_PAIR #define BAM_FMUNMAP 8 // the mate is unmapped #define BAM_FREVERSE 16 // the read is mapped to the reverse strand #define BAM_FMREVERSE 32 // the mate is mapped to the reverse strand #define BAM_FREAD1 64 // this is read1 #define BAM_FREAD2 128 // this is read2 #define BAM_FSECONDARY 256 // not primary alignment #define BAM_FQCFAIL 512 // QC failure #define BAM_FDUP 1024 // optical or PCR duplicate //---------- // // prototypes for private functions // //---------- static void print_query_bases (FILE* f, seq* seq2, unspos pos2, unspos length, int softMasked); static void print_query_quals (FILE* f, seq* seq2, unspos pos2, unspos length, int softMasked); //---------- // // sam_rg_tags-- // Parse a readgroup string and extract the tags that should be attached to // each read. // //---------- // // Arguments: // char* readGroup: The readgroup string (e.g. "ID:TRWFT SM:BGDNCSA32"). // char** errorText: Place to return any error text. The string pointer // .. returned here is local to THIS routine, and does // .. not need to be deallocated. This pointer may be // .. NULL, in which case no error text is returned. // // Returns: // A pointer to the RG tags, allocated from the heap. The caller is // responsible for deallocating that string. If there is any error in parsing // the string, NULLL is returned and the explaination of the error is copied // to errorText (unless it is NULL). // //---------- char* sam_rg_tags (char* readGroup, char** errorText) { char* idTag; int idLen; u32 bytesNeeded; char* s, *ss; ////////// // parse and validate the important fields ////////// idTag = find_tabbed_tag (readGroup, "ID"); if (idTag == NULL) { if (errorText != NULL) *errorText = "ID is a required field"; return NULL; } idLen = tabbed_tag_length (idTag); if (idLen <= 3) { if (errorText != NULL) *errorText = "ID field cannot be empty"; return NULL; } // nota bene: SAM spec 0.1.2-draft 20090820 specified SM as a required // tag. But SAM spec 1.4 shows it as an optional field. //smTag = find_tabbed_tag (readGroup, "SM"); //if (smTag == NULL) // { // if (errorText != NULL) *errorText = "SM is a required field"; // return NULL; // } //smLen = tabbed_tag_length (smTag); //if (smLen <= 3) // { // if (errorText != NULL) *errorText = "SM field cannot be empty"; // return NULL; // } // nota bene: SAM spec 1.4 appears to indicate that LB and PU tags don't // have to be propagated from the RG header line to alignment // records //lbTag = find_tabbed_tag (readGroup, "LB"); //lbLen = 0; // (placate complier) //if (lbTag != NULL) // { // lbLen = tabbed_tag_length (lbTag); // if (lbLen <= 3) // { // if (errorText != NULL) *errorText = "LB field cannot be empty"; // return NULL; // } // } //puTag = find_tabbed_tag (readGroup, "PU"); //puLen = 0; // (placate complier) //if (puTag != NULL) // { // puLen = tabbed_tag_length (puTag); // if (puLen <= 3) // { // if (errorText != NULL) *errorText = "PU field cannot be empty"; // return NULL; // } // } ////////// // collect the ID, LB and PU fields into a single string ////////// bytesNeeded = idLen + 1; //if (lbTag != NULL) bytesNeeded += 1 + lbLen; //if (puTag != NULL) bytesNeeded += 1 + puLen; s = ss = malloc_or_die ("sam_rg_tags", bytesNeeded); strncpy (/*to*/ ss, /*from*/ idTag, idLen); ss += idLen; //if (lbTag != NULL) // { // *(ss++) = '\t'; // strncpy (/*to*/ ss, /*from*/ lbTag, lbLen); // ss += lbLen; // } //if (puTag != NULL) // { // *(ss++) = '\t'; // strncpy (/*to*/ ss, /*from*/ puTag, puLen); // ss += puLen; // } *ss = 0; if (errorText != NULL) *errorText = NULL; return s; } //---------- // // print_sam_job_header-- // Print sam format job header. // //---------- static int headerPrinted = false; void print_sam_job_header (FILE* f, char* readGroup) { fprintf (f, "@HD\tVN:1.0\tSO:unsorted\n"); if (readGroup != NULL) fprintf (f, "@RG\t%s\n", readGroup); headerPrinted = false; } //---------- // // print_sam_header-- // Print sam format query header. // //---------- void print_sam_header (FILE* f, seq* seq1, arg_dont_complain(seq* seq2)) { seqpartition* sp1 = &seq1->partition; partition* p; u32 ix; char* name1; if (headerPrinted) return; // if seq1 is not partitioned, just print the (single) header name if (sp1->p == NULL) // sequence 1 is not partitioned { name1 = (seq1->useFullNames)? seq1->header : seq1->shortHeader; if ((name1 == NULL) || (name1[0] == 0)) name1 = "seq1"; fprintf (f, "@SQ\tSN:%s\tLN:" unsposFmt "\n", name1, seq1->trueLen); } // otherwise, seq1 is partitioned, so print the header name for each // partition else { p = sp1->p; for (ix=0 ; ixlen ; ix++) fprintf (f, "@SQ\tSN:%s\tLN:" unsposFmt "\n", &sp1->pool[p[ix].header], p[ix].trueLen); } headerPrinted = true; } //---------- // // print_sam_align_list-- // Print a list of gapped alignments in sam format. // //---------- // // Arguments: // FILE* f: The file to print to. // alignel* alignList: The list of alignments to print. // seq* seq1: One sequence. // seq* seq2: Another sequence. // int softMasked: true => sequence ends should be soft masked // false => sequence ends should be hard masked // char* rgTags: Additional tags for RG (read group). This is a // .. valid string per the format described in the SAM // .. spec. This may be NULL. // // Returns: // (nothing) // //---------- void print_sam_align_list (FILE* f, alignel* alignList, seq* seq1, seq* seq2, int softMasked, char* rgTags) { alignel* a; for (a=alignList ; a!=NULL ; a=a->next) { print_sam_align (f, seq1, a->beg1-1, a->end1, seq2, a->beg2-1, a->end2, a->script, a->s, softMasked, rgTags); } } //---------- // // print_sam_align-- // Print a single gapped alignment in sam format. // //---------- // // Arguments: // FILE* f: The file to print to. // seq* seq1: One sequence. // unspos beg1, end1: Range of positions in sequence 1 (origin 0). // seq* seq2: Another sequence. // unspos beg2, end2: Range of positions in sequence 2 (origin 0). // editscript* script: The script describing the path the alignment takes // .. in the DP matrix. // score s: The alignment's score. // int softMasked: true => sequence ends should be soft masked // false => sequence ends should be hard masked // char* rgTags: Additional tags for RG (read group). This is a // .. valid string per the format described in the SAM // .. spec. This may be NULL. // // Returns: // (nothing) // //---------- void print_sam_align (FILE* f, seq* seq1, unspos beg1, unspos end1, seq* seq2, unspos beg2, unspos end2, editscript* script, arg_dont_complain(score s), int softMasked, char* rgTags) { seqpartition* sp1 = &seq1->partition; seqpartition* sp2 = &seq2->partition; partition* part; unspos height, width, i, j, prevI, prevJ, run; u32 opIx; unspos len2; char* name1, *name2; unspos offset1, offset2, start1, start2; unspos startLoc1, startLoc2; unspos seq2Len, seq2True; int flag; char maskCh; unspos preMask, postMask, tmp; if (seq1->revCompFlags != rcf_forward) suicide ("attempt to print - strand or complement for sequence 1 in print_sam_align"); beg1++; // (internally, we want origin 1, inclusive) beg2++; height = end1 - beg1 + 1; len2 = width = end2 - beg2 + 1; ////////// // figure out position offsets and names ////////// if (sp1->p == NULL) // sequence 1 is not partitioned { name1 = (seq1->useFullNames)? seq1->header : seq1->shortHeader; if ((name1 == NULL) || (name1[0] == 0)) name1 = "seq1"; offset1 = 0; startLoc1 = seq1->startLoc; } else // sequence 1 is partitioned { part = lookup_partition (seq1, beg1-1); name1 = &sp1->pool[part->header]; offset1 = part->sepBefore + 1; startLoc1 = part->startLoc; } if (sp2->p == NULL) // sequence 2 is not partitioned { name2 = (seq2->useFullNames)? seq2->header : seq2->shortHeader; if ((name2 == NULL) || (name2[0] == 0)) name2 = "seq2"; offset2 = 0; seq2Len = seq2->len; seq2True = seq2->trueLen; startLoc2 = seq2->startLoc; } else // sequence 2 is partitioned { part = lookup_partition (seq2, beg2-1); name2 = &sp2->pool[part->header]; offset2 = part->sepBefore + 1; seq2Len = part->sepAfter - offset2; seq2True = part->trueLen; startLoc2 = part->startLoc; } ////////// // print sam line (field names indicate below are per sam spec) ////////// start1 = beg1-1 - offset1 + startLoc1; if ((seq2->revCompFlags & rcf_rev) == 0) { start2 = beg2-1 - offset2 + startLoc2; end2 = start2-1 + len2; flag = 0; } else { start2 = startLoc2 + offset2 + (seq2Len - beg2) - (len2-1); end2 = startLoc2 + offset2 + (seq2Len - beg2); flag = BAM_FREVERSE; } // print qname, flag, rname, pos and mapq fprintf (f, "%s\t%d\t%s\t" unsposFmt "\t%d\t", name2, flag, name1, start1, 255); // print cigar maskCh = (softMasked)? 'S' : 'H'; preMask = postMask = 0; if (start2 > 1) preMask = start2 - 1; if (end2 < seq2True) postMask = seq2True - end2; if ((seq2->revCompFlags & rcf_rev) != 0) { tmp = preMask; preMask = postMask; postMask = tmp; } if (preMask != 0) fprintf (f, unsposFmt "%c", preMask, maskCh); opIx = 0; for (i=j=0 ; (i< height)||(j 0) { fprintf (f, unsposFmt "M", run); i += run; j += run; } if ((i < height) || (j < width)) { prevI = i; prevJ = j; edit_script_indel_len (script, &opIx, &i, &j); if (i > prevI) fprintf (f, unsposFmt "D", i - prevI); if (j > prevJ) fprintf (f, unsposFmt "I", j - prevJ); } } if (postMask != 0) fprintf (f, unsposFmt "%c", postMask, maskCh); // print mrnm, mpos, and isize fprintf (f, "\t%s\t%d\t%d\t", "*", 0, 0); // print seq (data from sequence 2) print_query_bases (f, seq2, beg2-1, len2, softMasked); // print qual (if we have no qual data, we print "*") if (seq2->vq == NULL) fprintf (f, "\t%s", "*"); else { fprintf (f, "\t"); print_query_quals (f, seq2, beg2-1, len2, softMasked); } // print tags if (rgTags != NULL) fprintf (f, "\t%s", rgTags); fprintf (f, "\n"); } //---------- // // print_sam_match-- // Print an hsp in sam format. // //---------- // // Arguments: // FILE* f: The file to print to. // seq* seq1: One sequence. // unspos pos1: The position, in seq1, of first character in the // .. match (origin-0). // seq* seq2: Another sequence. // unspos pos1: The position, in seq2, of first character in the // .. match (origin-0). // unspos length: The number of nucleotides in the HSP. // score s: The HSP's score. // int softMasked: true => sequence ends should be soft masked // false => sequence ends should be hard masked // char* rgTags: Additional tags for RG (read group). This is a valid // .. string per the format described in the SAM spec. // .. This may be NULL. // // Returns: // (nothing) // //---------- void print_sam_match (FILE* f, seq* seq1, unspos pos1, seq* seq2, unspos pos2, unspos length, arg_dont_complain(score s), int softMasked, char* rgTags) { seqpartition* sp1 = &seq1->partition; seqpartition* sp2 = &seq2->partition; partition* part; char* name1, *name2; unspos offset1, offset2, start1, start2, end2; unspos startLoc1, startLoc2; unspos seq2Len, seq2True; int flag; char maskCh; unspos preMask, postMask, tmp; if (seq1->revCompFlags != rcf_forward) suicide ("attempt to print - strand or complement for sequence 1 in print_sam_match"); ////////// // figure out position offsets and names ////////// if (sp1->p == NULL) // sequence 1 is not partitioned { name1 = (seq1->useFullNames)? seq1->header : seq1->shortHeader; if ((name1 == NULL) || (name1[0] == 0)) name1 = "seq1"; offset1 = 0; startLoc1 = seq1->startLoc; } else // sequence 1 is partitioned { part = lookup_partition (seq1, pos1); name1 = &sp1->pool[part->header]; offset1 = part->sepBefore + 1; startLoc1 = part->startLoc; } if (sp2->p == NULL) // sequence 2 is not partitioned { name2 = (seq2->useFullNames)? seq2->header : seq2->shortHeader; if ((name2 == NULL) || (name2[0] == 0)) name2 = "seq2"; offset2 = 0; seq2Len = seq2->len; seq2True = seq2->trueLen; startLoc2 = seq2->startLoc; } else // sequence 2 is partitioned { part = lookup_partition (seq2, pos2); name2 = &sp2->pool[part->header]; offset2 = part->sepBefore + 1; seq2Len = part->sepAfter - offset2; seq2True = part->trueLen; startLoc2 = part->startLoc; } ////////// // print sam line (field names indicate below are per sam spec) ////////// start1 = pos1 - offset1 + startLoc1; if ((seq2->revCompFlags & rcf_rev) == 0) { start2 = pos2 - offset2 + startLoc2; end2 = start2-1 + length; flag = 0; } else { start2 = startLoc2 + offset2 + (seq2Len - pos2) - length; end2 = startLoc2 + offset2 + (seq2Len - pos2) - 1; flag = BAM_FREVERSE; } // print qname, flag, rname, pos and mapq (field names per sam spec) fprintf (f, "%s\t%d\t%s\t" unsposFmt "\t%d\t", name2, flag, name1, start1, 255); // print cigar maskCh = (softMasked)? 'S' : 'H'; preMask = postMask = 0; if (start2 > 1) preMask = start2 - 1; if (end2 < seq2True) postMask = seq2True - end2; if ((seq2->revCompFlags & rcf_rev) != 0) { tmp = preMask; preMask = postMask; postMask = tmp; } if (preMask != 0) fprintf (f, unsposFmt "%c", preMask, maskCh); fprintf (f, unsposFmt "M", length); if (postMask != 0) fprintf (f, unsposFmt "%c", postMask, maskCh); // print mrnm, mpos, and isize fprintf (f, "\t%s\t%d\t%d\t", "*", 0, 0); // print seq (data from sequence 2) print_query_bases (f, seq2, pos2, length, softMasked); // print qual (if we have no qual data, we print "*") if (seq2->vq == NULL) fprintf (f, "\t%s", "*"); else { fprintf (f, "\t"); print_query_quals (f, seq2, pos2, length, softMasked); } // print rgTags if (rgTags != NULL) fprintf (f, "\t%s", rgTags); fprintf (f, "\n"); } //---------- // // print_query_bases-- // Print the "seq" field for sam format. // //---------- // // Arguments: // FILE* f: The file to print to. // seq* seq2: The query sequence. // unspos pos1: The position, in seq2, of first character in the // .. match (origin-0). // unspos length: The number of nucleotides in the HSP. // int softMasked: true => sequence ends should be soft masked // false => sequence ends should be hard masked // // Returns: // (nothing) // //---------- static void print_query_bases (FILE* f, seq* seq2, unspos pos2, unspos length, int softMasked) { seqpartition* sp2 = &seq2->partition; partition* part; u8* s2 = seq2->v + pos2; u8* ss2; unspos offset2, start2, end2; unspos startLoc2; unspos seq2True; unspos ix; ////////// // figure out position offsets, etc. ////////// if (sp2->p == NULL) // sequence 2 is not partitioned { offset2 = 0; startLoc2 = seq2->startLoc; seq2True = seq2->trueLen; } else // sequence 2 is partitioned { part = lookup_partition (seq2, pos2); offset2 = part->sepBefore + 1; startLoc2 = part->startLoc; seq2True = part->trueLen; } ////////// // print seq (data from sequence 2) ////////// start2 = pos2 - offset2 + startLoc2; end2 = start2-1 + length; if ((softMasked) && (start2 > 1)) { ss2 = seq2->v + pos2 - (start2-1); for (ix=0 ; ix sequence ends should be soft masked // false => sequence ends should be hard masked // // Returns: // (nothing) // //---------- static void print_query_quals (FILE* f, seq* seq2, unspos pos2, unspos length, int softMasked) { seqpartition* sp2 = &seq2->partition; partition* part; u8* s2 = seq2->vq + pos2; u8* ss2; unspos offset2, start2, end2; unspos startLoc2; unspos seq2True; unspos ix; ////////// // figure out position offsets, etc. ////////// if (sp2->p == NULL) // sequence 2 is not partitioned { offset2 = 0; startLoc2 = seq2->startLoc; seq2True = seq2->trueLen; } else // sequence 2 is partitioned { part = lookup_partition (seq2, pos2); offset2 = part->sepBefore + 1; startLoc2 = part->startLoc; seq2True = part->trueLen; } ////////// // print qual (qualities from sequence 2) ////////// start2 = pos2 - offset2 + startLoc2; end2 = start2-1 + length; if ((softMasked) && (start2 > 1)) { ss2 = seq2->vq + pos2 - (start2-1); for (ix=0 ; ix