2521d696f5073ce8cee3f59f423f161fdb77d550 max Fri Sep 25 02:48:37 2026 -0700 VCF tracks: the bigBed trackDb filters (filter.*, filterByRange, filterLimits, filterValues, filterType, filterText, filterLabel) now work on INFO fields, plus ID and QUAL. The field list and types come from the VCF header; the bigBed filter code is reused, bigBed behavior unchanged, refs #37617 diff --git src/hg/lib/vcfUi.c src/hg/lib/vcfUi.c index 7ba16bd1580..cc773906eb1 100644 --- src/hg/lib/vcfUi.c +++ src/hg/lib/vcfUi.c @@ -2,30 +2,31 @@ * between more than one CGI. */ /* Copyright (C) 2014 The Regents of the University of California * See kent/LICENSE or http://genome.ucsc.edu/license/ for licensing information. */ #include "common.h" #include "cheapcgi.h" #include "errCatch.h" #include "hCommon.h" #include "hui.h" #include "jsHelper.h" #include "vcf.h" #include "vcfUi.h" #include "knetUdc.h" #include "udc.h" +#include "asParse.h" INLINE char *nameOrDefault(char *thisName, char *defaultVal) /* If thisName is not a placeholder value, return it; otherwise return default. */ { if (isNotEmpty(thisName) && !sameString(thisName, ".")) return thisName; return defaultVal; } #define VCF_HAPLOSORT_DEFAULT_DESC "middle variant in viewing window" static void vcfCfgHaplotypeCenterHiddens(char *track, char *ctrName, char *ctrChrom, int ctrPos) /* Make hidden form inputs and button for setting the center variant for haplotype * clustering/sorting in hgTracks. */ { @@ -183,31 +184,31 @@ "if (this.value == '"VCF_HAP_METHOD_CENTER_WEIGHTED"') {" " $('#leafShapeContainer').show();" " $('#sampleColorContainer').hide();" "} else if (this.value == '"VCF_HAP_METHOD_TREE_FILE"') {" " $('#sampleColorContainer').show();" " $('#leafShapeContainer').hide();" "} else {" " $('#sampleColorContainer').hide();" " $('#leafShapeContainer').hide();" "}});\n", varName); } } //TODO: share this code w/hgTracks, hgc in hg/lib/vcfFile.c -static struct vcfFile *vcfHopefullyOpenHeader(struct cart *cart, struct trackDb *tdb) +static struct vcfFile *vcfHopefullyOpenHeaderUncached(struct cart *cart, struct trackDb *tdb) /* Defend against network errors and return the vcfFile object with header data, or NULL. */ { knetUdcInstall(); if (udcCacheTimeout() < 300) udcSetCacheTimeout(300); char *fileOrUrl = trackDbSetting(tdb, "bigDataUrl"); if (isEmpty(fileOrUrl)) { char *db = cartString(cart, "db"); char *table = tdb->table; char *dbTableName = trackDbSetting(tdb, "dbTableName"); struct sqlConnection *conn; if (isCustomTrack(tdb->track) && isNotEmpty(dbTableName)) { conn = hAllocConn(CUSTOM_TRASH); @@ -233,30 +234,93 @@ if (startsWithWord("vcfTabix", tdb->type)) vcff = vcfTabixFileMayOpen(fileOrUrl, NULL, 0, 0, vcfMaxErr, -1); else vcff = vcfFileMayOpen(fileOrUrl, NULL, 0, 0, vcfMaxErr, -1, FALSE); } errCatchEnd(errCatch); if (errCatch->gotError) { if (isNotEmpty(errCatch->message->string)) warn("unable to open %s: %s", fileOrUrl, errCatch->message->string); } errCatchFree(&errCatch); return vcff; } +static struct vcfFile *vcfHopefullyOpenHeader(struct cart *cart, struct trackDb *tdb) +/* Return the vcfFile object with header data, or NULL, opening the file only once per track + * in this CGI run: the config UI and the generic trackDb filter code both need the header. */ +{ +static struct hash *vcffCache = NULL; +if (vcffCache == NULL) + vcffCache = hashNew(0); +struct hashEl *hel = hashLookup(vcffCache, tdb->track); +if (hel != NULL) + return hel->val; +struct vcfFile *vcff = vcfHopefullyOpenHeaderUncached(cart, tdb); +hashAdd(vcffCache, tdb->track, vcff); +return vcff; +} + +static struct asColumn *vcfFilterAsColumn(char *name, char *comment, char *asType) +/* Make one pseudo-autoSql column for vcfInfoAsObj. */ +{ +struct asColumn *col; +AllocVar(col); +col->name = cloneString(name); +col->comment = cloneString(isNotEmpty(comment) ? comment : name); +col->lowType = asTypeFindLow(asType); +return col; +} + +struct asObject *vcfInfoAsObj(struct cart *cart, struct trackDb *tdb) +/* Return a pseudo-autoSql object that describes the fields of a VCF track that can be used by the + * generic trackDb filters (filter.*, filterText.*, filterValues.* etc): one column per INFO + * definition in the VCF header, named by the INFO key, with the header's Description as comment, + * followed by the fixed columns ID and QUAL. Returns NULL if the file cannot be opened. */ +{ +struct vcfFile *vcff = vcfHopefullyOpenHeader(cart, tdb); +if (vcff == NULL) + return NULL; +struct asObject *asObj; +AllocVar(asObj); +asObj->name = cloneString("vcfInfo"); +asObj->comment = cloneString("VCF INFO fields"); +struct hash *seen = hashNew(0); +struct vcfInfoDef *def; +for (def = vcff->infoDefs; def != NULL; def = def->next) + { + char *asType = "string"; + if (def->type == vcfInfoInteger || def->type == vcfInfoFlag) + asType = "int"; + else if (def->type == vcfInfoFloat) + asType = "double"; + else if (def->type == vcfInfoCharacter) + asType = "char"; + slAddHead(&asObj->columnList, vcfFilterAsColumn(def->key, def->description, asType)); + hashStore(seen, def->key); + } +// If an INFO key has the same name as a fixed column, the INFO field wins, also in hgTracks +if (!hashLookup(seen, "ID")) + slAddHead(&asObj->columnList, vcfFilterAsColumn("ID", "Variant ID", "string")); +if (!hashLookup(seen, "QUAL")) + slAddHead(&asObj->columnList, vcfFilterAsColumn("QUAL", "Quality score (QUAL)", "double")); +slReverse(&asObj->columnList); +hashFree(&seen); +return asObj; +} + static void vcfCfgHapClusterEnable(struct cart *cart, struct trackDb *tdb, char *name, boolean parentLevel) /* Let the user enable/disable haplotype sorting display. */ { boolean hapClustEnabled = cartOrTdbBoolean(cart, tdb, VCF_HAP_ENABLED_VAR, TRUE); char cartVar[1024]; safef(cartVar, sizeof(cartVar), "%s." VCF_HAP_ENABLED_VAR, name); cgiMakeCheckBox(cartVar, hapClustEnabled); printf("Enable %s sorting display
\n", vcfHaplotypeOrSample(cart)); } static void vcfCfgHapClusterColor(struct cart *cart, struct trackDb *tdb, char *name, boolean parentLevel) /* Let the user choose how to color the sorted haplotypes. */ { @@ -723,30 +787,32 @@ { vcfCfgPhasedTrioUi(cart, tdb, vcff, name, parentLevel); } if (differentString(tdb->track,"evsEsp6500") && (doVcfFilterUi || doVcfQualUi)) { puts("

Filters

"); if (doVcfQualUi) vcfCfgMinQual(cart, tdb, vcff, name, parentLevel); if (doVcfFilterUi) vcfCfgFilterColumn(cart, tdb, vcff, name, parentLevel); } if (doVcfMafUi) vcfCfgMinAlleleFreq(cart, tdb, vcff, name, parentLevel); if (doVcfMinAcUi) vcfCfgMinAc(cart, tdb, vcff, name, parentLevel); + // The generic trackDb filters (filter., filterValues., ...), same UI as bigBed + trackDbFiltersCfgUi(cartString(cart, "db"), cart, tdb, name, FALSE); } else { printf("Sorry, couldn't access VCF file.
\n"); } puts(""); if (boxed && fileExists(hHelpFile("hgVcfTrackHelp"))) printf("VCF configuration help"); printf(""); wigOption(cart, name, title, tdb); if (!boxed && fileExists(hHelpFile("hgVcfTrackHelp")))