diff --git a/plugins/alignments/src/BamAdapter/MismatchParser.ts b/plugins/alignments/src/BamAdapter/MismatchParser.ts index c40e2441191..a17e007bfbf 100644 --- a/plugins/alignments/src/BamAdapter/MismatchParser.ts +++ b/plugins/alignments/src/BamAdapter/MismatchParser.ts @@ -1,4 +1,5 @@ import { revcom } from '@jbrowse/core/util' + export interface Mismatch { qual?: number start: number @@ -11,9 +12,12 @@ export interface Mismatch { } const mdRegex = new RegExp(/(\d+|\^[a-z]+|[a-z])/gi) const modificationRegex = new RegExp(/([A-Z])([-+])([^,.?]+)([.?])?/) +const cigarRegex = new RegExp(/([MIDNSHPX=])/) + export function parseCigar(cigar = '') { - return cigar.split(/([MIDNSHPX=])/).slice(0, -1) + return cigar.split(cigarRegex).slice(0, -1) } + export function cigarToMismatches( ops: string[], seq?: string, @@ -339,3 +343,47 @@ export function getModificationTypes(mm: string) { }) .flat() } + +export function getOrientedCigar(flip: boolean, cigar: string[]) { + if (flip) { + const ret = [] + for (let i = 0; i < cigar.length; i += 2) { + const len = cigar[i] + let op = cigar[i + 1] + if (op === 'D') { + op = 'I' + } else if (op === 'I') { + op = 'D' + } + ret.push(len) + ret.push(op) + } + return ret + } else { + return cigar + } +} + +export function getOrientedMismatches(flip: boolean, cigar: string) { + const mismatches = getMismatches(cigar) + if (flip) { + let startReadjuster = 0 + return mismatches.map(m => { + if (m.type === 'insertion') { + m.type = 'deletion' + m.length = +m.base + m.start += startReadjuster + startReadjuster += m.length + } else if (m.type === 'deletion') { + const len = m.length + m.type = 'insertion' + m.base = `${len}` + m.length = 0 + m.start += startReadjuster + startReadjuster -= len + } + return m + }) + } + return mismatches +} diff --git a/plugins/comparative-adapters/src/PAFAdapter/PAFAdapter.ts b/plugins/comparative-adapters/src/PAFAdapter/PAFAdapter.ts index 5f6c5d33571..4081f4790bb 100644 --- a/plugins/comparative-adapters/src/PAFAdapter/PAFAdapter.ts +++ b/plugins/comparative-adapters/src/PAFAdapter/PAFAdapter.ts @@ -16,7 +16,7 @@ import { MismatchParser } from '@jbrowse/plugin-alignments' // locals import { zip, isGzip } from '../util' -const { getMismatches } = MismatchParser +const { getOrientedMismatches } = MismatchParser export interface PAFRecord { qname: string @@ -131,30 +131,6 @@ function weightedMean(tuples: [number, number][]) { return valueSum / weightSum } -function getOrientedMismatches(flip: boolean, cigar: string) { - const mismatches = getMismatches(cigar) - if (flip) { - let startReadjuster = 0 - return mismatches.map(m => { - if (m.type === 'insertion') { - m.type = 'deletion' - m.length = +m.base - m.start += startReadjuster - startReadjuster += m.length - } else if (m.type === 'deletion') { - const len = m.length - m.type = 'insertion' - m.base = `${len}` - m.length = 0 - m.start += startReadjuster - startReadjuster -= len - } - return m - }) - } - return mismatches -} - class SyntenyFeature extends SimpleFeature { // eslint-disable-next-line @typescript-eslint/no-explicit-any get(arg: string): any { @@ -332,13 +308,13 @@ export default class PAFAdapter extends BaseFeatureDataAdapter { end, type: 'match', refName, - strand, // : !flip ? strand * -1 : strand, + strand, // this is a special property of how to interpret CIGAR on PAF, - // intrinsic to the data format. the CIGAR is read backwards - // for features aligning to the negative strand of the target, - // which is actually different than how it works in e.g. - // BAM/SAM (which is visible during alignments track read vs ref) + // intrinsic to the data format. the CIGAR is read backwards for + // features aligning to the negative strand of the target, which + // is actually different than how it works in e.g. BAM/SAM (which + // is visible during alignments track read vs ref) revCigar: true, // depending on whether the query or target is queried, the diff --git a/plugins/linear-comparative-view/src/LGVSyntenyDisplay/stateModelFactory.ts b/plugins/linear-comparative-view/src/LGVSyntenyDisplay/stateModelFactory.ts index 87804bcd2c6..79a88282143 100644 --- a/plugins/linear-comparative-view/src/LGVSyntenyDisplay/stateModelFactory.ts +++ b/plugins/linear-comparative-view/src/LGVSyntenyDisplay/stateModelFactory.ts @@ -20,12 +20,13 @@ import { LinearSyntenyViewModel } from '../LinearSyntenyView/model' type LSV = LinearSyntenyViewModel -const { parseCigar } = MismatchParser +const { parseCigar, getOrientedCigar } = MismatchParser -function findPosInCigar(cigar: string[], x: number) { +function findPosInCigar(inCigar: string[], flip: boolean, x: number) { let featX = 0 let mateX = 0 - for (let i = 0; i < cigar.length; i += 2) { + const cigar = getOrientedCigar(flip, inCigar) + for (let i = 0; i < cigar.length; i++) { const len = +cigar[i] const op = cigar[i + 1] const min = Math.min(len, x - featX) @@ -57,6 +58,7 @@ async function navToSynteny(feature: Feature, self: any) { const featStart = feature.get('start') const featEnd = feature.get('end') const mate = feature.get('mate') + const flip = feature.get('flipInsDel') const mateStart = mate.start const mateEnd = mate.end const mateAsm = mate.assemblyName @@ -69,16 +71,16 @@ async function navToSynteny(feature: Feature, self: any) { let rFeatStart = featStart let rFeatEnd = featStart - if (cigar.length) { - const [featStartX, mateStartX] = findPosInCigar(cigar, regStart - featStart) - const [featEndX, mateEndX] = findPosInCigar(cigar, regEnd - featStart) + if (cigar) { + const [fStartX, mStartX] = findPosInCigar(cigar, flip, regStart - featStart) + const [fEndX, mEndX] = findPosInCigar(cigar, flip, regEnd - featStart) // avoid multiply by 0 with strand undefined const flipper = strand === -1 ? -1 : 1 - rFeatStart = featStart + featStartX - rFeatEnd = featStart + featEndX - rMateStart = mateStart + mateStartX * flipper - rMateEnd = mateStart + mateEndX * flipper + rFeatStart = featStart + fStartX + rFeatEnd = featStart + fEndX + rMateStart = mateStart + mStartX * flipper + rMateEnd = mateStart + mEndX * flipper } else { rFeatEnd = featEnd rMateEnd = mateEnd