Download tools/build_source_hash.py from infernogod/enzyme-model: direct link, hf CLI and curl.
- Browser
- Download file 6.86 kB
-
https://huggingface.co/infernogod/enzyme-model/resolve/main/tools/build_source_hash.py
- Command line
-
hf download hf://infernogod/enzyme-model/tools/build_source_hash.py
-
curl -L -o build_source_hash.py https://huggingface.co/infernogod/enzyme-model/resolve/main/tools/build_source_hash.py
6.86 kB
| """v24: exact-sequence label table with a source priority measured on the training set. | |
| For every sequence in the disclosed external sources, the label is taken from the first source that has one, in the order | |
| BRENDA 2026.1 > BRENDA 2023.1 > CatPred-DB > CataPro > DLKcat > PDB/SIFTS > UniProtKB unreviewed (current EC) > ESP > ESP (current EC) | |
| (on 14,122 training proteins that are not in Swiss-Prot, BRENDA's EC is right 95% of the time when it disagrees with the | |
| current UniProt EC, which is right 2.5% of the time; this order scores 0.974 against the official training labels). | |
| Swiss-Prot and train sequences are not in this table: identical train/Swiss-Prot sequences keep their own labels. | |
| Output: <model>/weights/srchash.npz (h: sorted uint64 MD5 prefixes, s: uint32 EC-set id) + srchash_sets.json | |
| Usage: python3 build_source_hash.py --esm ~/enzyme-esm --catapro <CataPro/datasets> --model ~/enzyme-model [--exclude FASTA] | |
| """ | |
| import argparse, collections, gzip, hashlib, json, re | |
| from pathlib import Path | |
| import numpy as np | |
| import pandas as pd | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument('--esm', required=True); ap.add_argument('--catapro', required=True); ap.add_argument('--model', required=True) | |
| ap.add_argument('--exclude') | |
| a = ap.parse_args() | |
| E = Path(a.esm) | |
| VOC = set(json.load(open(Path(a.model) / 'ec_vocabulary.json'))) | |
| ORDER = ['B6', 'BR', 'CP', 'CT', 'DK', 'PD', 'TR', 'ES', 'E2'] | |
| def ecs(x): | |
| return [e.strip() for e in re.split(r'[;,]', str(x)) if e.strip() in VOC] | |
| def h(s): | |
| return int(hashlib.md5(s.encode()).hexdigest()[:16], 16) | |
| lab = collections.defaultdict(lambda: collections.defaultdict(set)) # hash -> source -> ECs (non-UniProt sources) | |
| br = collections.defaultdict(set) | |
| for line in open(E / 'brenda_acc_ec.tsv'): | |
| if '\t' in line: | |
| acc, e = line.rstrip('\n').split('\t'); br[acc].update(ecs(e)) | |
| sec = collections.defaultdict(set) | |
| for line in open(E / 'round3/sifts_acc_ec.tsv'): | |
| p = line.rstrip('\n').split('\t') | |
| if len(p) >= 2: sec[p[0]].update(ecs(p[1])) | |
| b6 = collections.defaultdict(set) # BRENDA release 2026.1 (r5/brenda26_acc_ec.tsv) | |
| for line in open(E / 'r5/brenda26_acc_ec.tsv'): | |
| acc, e = line.rstrip('\n').split('\t'); b6[acc].update(ecs(e)) | |
| need = set(br) | set(sec) | set(b6) | |
| acc2h = collections.defaultdict(set) # an accession can map to several sequence versions | |
| # accession -> sequence fingerprint for BRENDA / SIFTS accessions: UniProtKB unreviewed (fetch_uniprot_hash.py output) | |
| for p in sorted((E / 'uphash').rglob('*.tsv.gz')): | |
| with gzip.open(p, 'rt') as f: | |
| for line in f: | |
| acc, hx = line.split('\t', 2)[:2] | |
| if acc in need: acc2h[acc].add(int(hx, 16)) | |
| sp = pd.read_csv(E / 'sprot.tsv.gz', sep='\t'); sp.columns = ['acc', 'ec', 'seq'] | |
| sp_h = set() | |
| for acc, e, s in zip(sp.acc, sp.ec, sp.seq): | |
| k = h(s) | |
| if ecs(e): sp_h.add(k) # Swiss-Prot entries with a task EC are in the search reference already | |
| if acc in need: acc2h[acc].add(k) | |
| for f in [E / 'trembl_ec.tsv.gz', E / 'trembl_ev2s.tsv.gz']: | |
| t = pd.read_csv(f, sep='\t'); t.columns = ['acc', 'ec', 'seq'] | |
| for acc, s in zip(t.acc, t.seq): | |
| if acc in need and isinstance(s, str): acc2h[acc].add(h(s)) | |
| cur, buf = None, [] | |
| for line in list(open(E / 'brenda_missing.fasta')) + ['>end|end|\n']: | |
| if line.startswith('>'): | |
| if cur: acc2h[cur].add(h(''.join(buf))) | |
| cur = line[1:].split('|')[1] if '|' in line else line[1:].split()[0]; buf = [] | |
| else: | |
| buf.append(line.strip()) | |
| bo = pd.read_csv(E / 'round3/brenda_uniparc.tsv', sep='\t') | |
| for acc, s in zip(bo.accession, bo.sequence): | |
| acc2h[acc].add(h(s)) | |
| sn = pd.read_csv(E / 'round3/sifts_new.tsv', sep='\t') | |
| for acc, s in zip(sn.iloc[:, 0], sn.iloc[:, -1]): | |
| if isinstance(s, str): acc2h[acc].add(h(s)) | |
| t6 = pd.read_csv(E / 'r5/brenda26_seqs.tsv', sep='\t') | |
| for acc, s in zip(t6.accession, t6.sequence): | |
| if isinstance(s, str): acc2h[acc].add(h(s)) | |
| for acc, e in b6.items(): | |
| for k in acc2h.get(acc, ()): | |
| if e: lab[k]['B6'].update(e) | |
| print('BRENDA 2026.1 accessions mapped', sum(a in acc2h for a in b6), 'of', len(b6), flush=True) | |
| for acc, e in br.items(): | |
| for k in acc2h.get(acc, ()): | |
| if e: lab[k]['BR'].update(e) | |
| for acc, e in sec.items(): | |
| for k in acc2h.get(acc, ()): | |
| if e: lab[k]['PD'].update(e) | |
| print('BRENDA accessions mapped', sum(a in acc2h for a in br), 'of', len(br), flush=True) | |
| cp = pd.read_csv(E / 'catpred/catpred_seq_ec.tsv.gz', sep='\t') | |
| for e, s in zip(cp.ec, cp.sequence): | |
| if isinstance(s, str) and ecs(e): lab[h(s)]['CP'].update(ecs(e)) | |
| for f in Path(a.catapro).glob('*fold.csv'): | |
| c = pd.read_csv(f) | |
| for e, s in zip(c.EC, c.Sequence): | |
| if isinstance(s, str) and ecs(e): lab[h(s)]['CT'].update(ecs(e)) | |
| k_ = pd.read_csv(E / 'kin_esp_seq_ec.tsv.gz', sep='\t') | |
| for src, e, s in zip(k_.src, k_.ec, k_.sequence): | |
| if isinstance(s, str) and ecs(e): lab[h(s)]['DK' if src == 'dlk' else 'ES'].update(ecs(e)) | |
| en = pd.read_csv(E / 'round3/esp_noec_uniprot.tsv', sep='\t').dropna(subset=['EC number']) | |
| for e, s in zip(en['EC number'], en.Sequence): | |
| if ecs(e): lab[h(s)]['E2'].update(ecs(e)) | |
| # current UniProtKB unreviewed EC from the fingerprint index (weights/uphash.npz) | |
| z = np.load(Path(a.model) / 'weights' / 'uphash.npz'); UH, US = z['h'], z['s'] | |
| usets = json.load(open(Path(a.model) / 'weights' / 'uphash_sets.json')) | |
| ex = set() | |
| if a.exclude: | |
| cur = [] | |
| for line in list(open(a.exclude)) + ['>']: | |
| if line.startswith('>'): | |
| if cur: ex.add(h(''.join(cur))) | |
| cur = [] | |
| else: | |
| cur.append(line.strip()) | |
| set_id, sets = {}, [] | |
| def sid(t): | |
| t = tuple(sorted(t)) | |
| if t not in set_id: | |
| set_id[t] = len(sets); sets.append(list(t)) | |
| return set_id[t] | |
| cnt = collections.Counter() | |
| # 1. sequences in a non-UniProt source: priority order, UniProt (TR) in its place | |
| H, S = [], [] | |
| for k, d in lab.items(): | |
| if k in sp_h or k in ex: | |
| continue | |
| j = int(np.searchsorted(UH, np.uint64(k))) | |
| if j < len(UH) and UH[j] == k: | |
| d['TR'] = set(usets[int(US[j])]) | |
| for src in ORDER: | |
| if d.get(src): | |
| H.append(k); S.append(sid(d[src])); cnt[src] += 1 | |
| break | |
| H = np.array(H, dtype=np.uint64); S = np.array(S, dtype=np.uint32) | |
| # 2. all other UniProt unreviewed sequences keep their current EC | |
| m = ~np.isin(UH, H) & ~np.isin(UH, np.array(sorted(sp_h | ex), dtype=np.uint64)) | |
| remap = np.array([sid(x) for x in usets], dtype=np.uint32) | |
| cnt['TR'] += int(m.sum()) | |
| H = np.concatenate([H, UH[m]]); S = np.concatenate([S, remap[US[m]]]) | |
| o = np.argsort(H, kind='stable'); H, S = H[o], S[o] | |
| w = Path(a.model) / 'weights' | |
| np.savez(w / 'srchash.npz', h=H, s=S) | |
| json.dump(sets, open(w / 'srchash_sets.json', 'w')) | |
| print(f'{len(H)} sequences, label source: {dict(cnt)}, excluded {len(ex)}') | |