896eb991fff50f44f18811a05171e36fa111163d braney Thu Sep 24 17:13:20 2026 -0700 quickLift: leave empty blocks out of a protein alignment before lifting it, refs #38249 The UniProt bigPsl files store block sizes in bases, and pslFromBigPsl divides them by three, so a block shorter than a codon loads with size 0. pslTransMap rejects such an alignment and aborts, which replaced the whole lifted SwissProt track with a BUG message. About one alignment in eight has a block like this. quickLiftPsl now lifts a copy with the empty blocks removed. quickLiftTester has a new case in the shape of Q96ME1-2. diff --git src/hg/lib/quickLift.c src/hg/lib/quickLift.c index c2769612503..5516bfe6e79 100644 --- src/hg/lib/quickLift.c +++ src/hg/lib/quickLift.c @@ -655,50 +655,101 @@ lifted->strand[2] = 0; } lifted->qStart /= 3; lifted->qEnd /= 3; lifted->qSize /= 3; lifted->qBaseInsert /= 3; for (i = 0; i < lifted->blockCount; i++) { lifted->blockSizes[i] /= 3; lifted->qStarts[i] /= 3; } return TRUE; } +static struct psl *pslWithoutEmptyBlocks(struct psl *psl) +/* A copy of a protein alignment with its zero-length blocks taken out, or NULL if it has + * none. The copy can be left with no blocks at all. The UniProt bigPsl files store block sizes in bases, and a block shorter than a + * codon comes out of pslFromBigPsl with size 0. Drawing does not mind, but pslTransMap + * checks the alignment after converting it to nucleotides and aborts on the empty block, + * which takes down every item in the track. About one alignment in eight has one. */ +{ +int i, j; + +for (i = 0; i < psl->blockCount; i++) + if (psl->blockSizes[i] == 0) + break; +if (i == psl->blockCount) + return NULL; + +struct psl *copy = pslClone(psl); +for (i = 0, j = 0; i < copy->blockCount; i++) + { + if (copy->blockSizes[i] == 0) + continue; + copy->blockSizes[j] = copy->blockSizes[i]; + copy->qStarts[j] = copy->qStarts[i]; + copy->tStarts[j] = copy->tStarts[i]; + j++; + } +copy->blockCount = j; +if (j == 0) + return copy; + +// Removing an end block moves the bounds, and pslIsProtein compares tEnd with the last +// block, so put them back in step. pslRecalcBounds does not know about protein units. +int last = j - 1; +int tStart = copy->tStarts[0]; +int tEnd = copy->tStarts[last] + 3 * copy->blockSizes[last]; +if (copy->strand[1] == '-') + reverseIntRange(&tStart, &tEnd, copy->tSize); +copy->tStart = tStart; +copy->tEnd = tEnd; +copy->qStart = copy->qStarts[0]; +copy->qEnd = copy->qStarts[last] + copy->blockSizes[last]; +pslComputeInsertCounts(copy); +return copy; +} + struct psl *quickLiftPsl(struct hash *chainHash, struct hash **pMapPsls, struct psl *psl) // Map the target side of an alignment from the other assembly onto our current reference. // The query side (the mRNA, EST or protein the alignment is to) is left alone. Returns // NULL if the alignment doesn't map. pMapPsls points at a hash of mapping alignments the // caller keeps across a run of items; point it at a NULL hash to start. { struct chain *chain = liftOverChainForRange(chainHash, psl->tName, psl->tStart, psl->tEnd); if (chain == NULL) return NULL; struct psl *mapPsl = mapPslForChain(pMapPsls, chain); // pslTransMap aborts when the two alignments disagree about the size of the sequence they // share. That means the chain and the track were built against different versions of the // other assembly, so drop the item rather than taking the CGI down with it. if (psl->tSize != mapPsl->qSize) return NULL; -struct psl *lifted = pslTransMap(pslTransMapNoOpts, psl, pslTypeUnspecified, - mapPsl, pslTypeUnspecified); +struct psl *trimmed = pslIsProtein(psl) ? pslWithoutEmptyBlocks(psl) : NULL; +if (trimmed && (trimmed->blockCount == 0)) + { + pslFree(&trimmed); + return NULL; + } +struct psl *lifted = pslTransMap(pslTransMapNoOpts, trimmed ? trimmed : psl, + pslTypeUnspecified, mapPsl, pslTypeUnspecified); +pslFree(&trimmed); if (lifted != NULL) { // before counting, so quickLiftPslCounts sees both sides in the same units if (pslIsProtein(psl)) quickLiftPslBackToProtein(lifted); quickLiftPslCounts(psl, lifted); } return lifted; } static struct chain *chainFromPsl(struct psl *psl) /* The inverse of chainToPsl. Score and id are the caller's to fill in, since an alignment * does not carry them. */ { struct chain *chain;