2070aedea88da26aee0af3e2fd867b67140b09b5 lrnassar Wed Aug 5 16:59:19 2026 -0700 Move the maf sequence-name splitter into jkweb so both summary tools share one copy. refs #37928 mafSplitSrcGetChrom existed as two independent copies, in hgLoadMafSummary.c and mafToBigMafSummary.c, and they had diverged: only the first had the GenArk accession rule added by 2db6bab8db0. It now lives in src/lib/maf.c, with the rules documented in maf.h. mafToBigMafSummary therefore picks up the GenArk rule it was missing, so a dotted GenArk name like GCA_009914755.4.CM034974.1 now keeps its accession version instead of dropping it, which matches what hprc90waySummary already holds. Also removes mafCompGetSrcDb and mafCompGetSrcName from maf.c and maf.h. Both were first-dot splitters with no callers anywhere in the tree. diff --git src/hg/utils/mafToBigMafSummary/mafToBigMafSummary.c src/hg/utils/mafToBigMafSummary/mafToBigMafSummary.c index ddc22a86a69..4d6a82d8299 100644 --- src/hg/utils/mafToBigMafSummary/mafToBigMafSummary.c +++ src/hg/utils/mafToBigMafSummary/mafToBigMafSummary.c @@ -1,273 +1,245 @@ /* mafToBigMafSummary - Convert a maf into bed3+4 input ready for bedToBigBed * to make a bigMaf summary (.bb) file. Companion to mafToBigMaf. */ /* The summary scoring/merging logic in this file is deliberately duplicated * from hgLoadMafSummary.c rather than refactored into a shared library. * That code is stable, hgLoadMafSummary is widely referenced from existing * makedocs, and a refactor would force retesting all of those pipelines for - * minimal payoff. If you change one, change the other. */ + * minimal payoff. If you change one, change the other. + * The sequence-name splitting that used to be duplicated here is the exception: + * it now lives in jkweb as mafSplitSrcGetChrom(), called by both tools. */ #include "common.h" #include "linefile.h" #include "hash.h" #include "options.h" #include "maf.h" #include "mafSummary.h" static struct optionSpec optionSpecs[] = { {"mergeGap", OPTION_INT}, {"minSize", OPTION_INT}, {"maxSize", OPTION_INT}, {"minSeqSize", OPTION_INT}, {NULL, 0} }; int mergeGap = 500; int minSize = 10000; int maxSize = 50000; int minSeqSize = 1; char *referenceDb = NULL; void usage() /* Explain usage and exit. */ { errAbort( "mafToBigMafSummary - Convert a maf into the bed3+4 input for a bigMaf summary file\n" "usage:\n" " mafToBigMafSummary referenceDb input.maf out.bed\n" "Pipe the output through 'sort -k1,1 -k2,2n', then run bedToBigBed:\n" " bedToBigBed -type=bed3+4 -as=mafSummary.as -tab out.sorted.bed \\\n" " referenceDb.chrom.sizes bigMafSummary.bb\n" "options:\n" " -mergeGap=N max size of gap to merge regions (default %d)\n" " -minSize=N merge blocks smaller than N (default %d)\n" " -maxSize=N break up blocks larger than N (default %d)\n" " -minSeqSize=N skip alignments when reference sequence is less than N\n" " (default %d)\n", mergeGap, minSize, maxSize, minSeqSize ); } double scorePairwise(struct mafAli *maf) /* generate score from 0.0 to 1.0 for an alignment pair */ /* Adapted from multiz scoring in hgTracks/mafTrack.c */ { int endB; /* end in the reference (master) genome */ int deltaB; int endT; /* end in the master genome maf text (includes gaps) */ double score; double minScore = -100.0, maxScore = 100.0; double scoreScale = 1.0 / (maxScore - minScore); struct mafComp *mcMaster = maf->components; endB = mcMaster->size; deltaB = endB; for (endT = 0; endT < maf->textSize; endT++) { if (deltaB <= 0) break; if (mcMaster->text[endT] != '-') deltaB -= 1; } score = mafScoreRangeMultiz(maf, 0, endT)/endB; score = (score - minScore) * scoreScale; if (score < 0.0) score = 0.0; if (score > 1.0) score = 1.0; return score; } void outputSummary(FILE *f, struct mafSummary *ms) /* Output a single summary block as bed3+4 (no SQL bin column). */ { mafSummaryTabOut(ms, f); } double mergeScores(struct mafSummary *ms1, struct mafSummary *ms2) /* Weighted-average score for two adjacent summary blocks (ms1 first). */ { double total = ms1->score * (ms1->chromEnd - ms1->chromStart) + ms2->score * (ms2->chromEnd - ms2->chromStart); return total / (ms2->chromEnd - ms1->chromStart); } struct mafComp *mafMaster(struct mafAli *maf, struct mafFile *mf, char *fileName) /* Get master component from maf. errAbort if not found. */ { struct mafComp *mcMaster = mafMayFindCompPrefix(maf, referenceDb, "."); if (mcMaster == NULL) errAbort("Couldn't find %s. sequence line %d of %s\n", referenceDb, mf->lf->lineIx, fileName); return mcMaster; } -char *mafSplitSrcGetChrom(char *src, char *database) -/* src can be in format chrom, db|chrom or db.chrom: split string on separator and return pointer to chrom. - * If database is non-NULL and src starts with "database.", strip exactly that prefix - * (so a database name like GCF_1234.3 with its own dot is handled correctly). - * Side effect: src is truncated at the separator (so it becomes just the db). */ -{ -char *pipe = strchr(src, '|'); -if (pipe) - { - *pipe = '\0'; - return pipe + 1; - } - -if (database != NULL) - { - int dbLen = strlen(database); - if (strncmp(src, database, dbLen) == 0 && src[dbLen] == '.') - { - src[dbLen] = '\0'; - return src + dbLen + 1; - } - } - -char *dot = strchr(src, '.'); -if (!dot) - return src; -*dot = '\0'; -return dot + 1; -} - long processMaf(struct mafAli *maf, struct hash *componentHash, FILE *f, struct mafFile *mf, char *fileName) /* Compute scores for each pairwise component in the maf and emit summary blocks. */ { struct mafComp *mc = NULL, *nextMc = NULL; struct mafSummary *ms, *msPending; struct mafAli pairMaf; long componentCount = 0; struct mafComp *mcMaster = mafMaster(maf, mf, fileName); struct mafComp *oldMasterNext = mcMaster->next; char *chrom; char src[256]; strcpy(src, mcMaster->src); chrom = mafSplitSrcGetChrom(src, referenceDb); for (mc = maf->components; mc != NULL; mc = nextMc) { nextMc = mc->next; if (sameString(mcMaster->src, mc->src) || mc->size == 0) continue; AllocVar(ms); ms->chrom = cloneString(chrom); ms->chromStart = mcMaster->start; ms->chromEnd = mcMaster->start + mcMaster->size; ms->src = cloneString(mc->src); mafSplitSrcGetChrom(ms->src, referenceDb); ZeroVar(&pairMaf); pairMaf.textSize = maf->textSize; pairMaf.components = mcMaster; mcMaster->next = mc; mc->next = NULL; ms->score = scorePairwise(&pairMaf); ms->leftStatus[0] = mc->leftStatus; ms->rightStatus[0] = mc->rightStatus; mcMaster->next = oldMasterNext; mc->next = nextMc; if ((msPending = (struct mafSummary *) hashFindVal(componentHash, ms->src)) != NULL) { if (sameString(ms->chrom, msPending->chrom) && (ms->chromStart+1 - msPending->chromEnd < mergeGap)) { ms->score = mergeScores(msPending, ms); ms->chromStart = msPending->chromStart; ms->leftStatus[0] = msPending->leftStatus[0]; ms->rightStatus[0] = ms->rightStatus[0]; } else outputSummary(f, msPending); hashRemove(componentHash, msPending->src); mafSummaryFree(&msPending); } if (ms->chromEnd - ms->chromStart > minSize) { outputSummary(f, ms); mafSummaryFree(&ms); } else hashAdd(componentHash, ms->src, ms); componentCount++; } return componentCount; } void flushSummaryBlocks(struct hash *componentHash, FILE *f) /* flush any pending summary blocks */ { struct mafSummary *ms; struct hashCookie hc = hashFirst(componentHash); while ((ms = (struct mafSummary *)hashNextVal(&hc)) != NULL) outputSummary(f, ms); } void mafToBigMafSummary(char *db, char *inMaf, char *outBed) /* mafToBigMafSummary - emit summary blocks for the maf as bed3+4. */ { long mafCount = 0, allMafCount = 0; struct mafComp *mcMaster = NULL; struct mafAli *maf; struct mafFile *mf = mafOpen(inMaf); FILE *f = mustOpen(outBed, "w"); long componentCount = 0; struct hash *componentHash = newHash(0); verbose(1, "Indexing and tabulating %s\n", inMaf); while ((maf = mafNext(mf)) != NULL) { mcMaster = mafMaster(maf, mf, inMaf); allMafCount++; if (mcMaster->srcSize < minSeqSize) continue; while (mcMaster->size > maxSize) { int end = mcMaster->start + maxSize; struct mafAli *subMaf = mafSubset(maf, mcMaster->src, mcMaster->start, end); verbose(3, "Splitting maf %s:%d len %d\n", mcMaster->src, mcMaster->start, mcMaster->size); componentCount += processMaf(subMaf, componentHash, f, mf, inMaf); mafAliFree(&subMaf); subMaf = mafSubset(maf, mcMaster->src, end, end + (mcMaster->size - maxSize)); mafAliFree(&maf); maf = subMaf; mcMaster = mafMaster(maf, mf, inMaf); } if (mcMaster->size != 0) componentCount += processMaf(maf, componentHash, f, mf, inMaf); mafAliFree(&maf); mafCount++; } mafFileFree(&mf); flushSummaryBlocks(componentHash, f); carefulClose(&f); verbose(1, "%ld components, %ld mafs from %s\n", componentCount, allMafCount, inMaf); } int main(int argc, char *argv[]) /* Process command line. */ { optionInit(&argc, argv, optionSpecs); mergeGap = optionInt("mergeGap", mergeGap); minSize = optionInt("minSize", minSize); maxSize = optionInt("maxSize", maxSize); minSeqSize = optionInt("minSeqSize", minSeqSize); if (argc != 4) usage(); referenceDb = argv[1]; mafToBigMafSummary(referenceDb, argv[2], argv[3]); return 0; }