Download model/utils/ndo_score.py from OneScience-Group/Chainsaw: direct link, hf CLI and curl.
- Browser
- Download file 4.07 kB
-
https://huggingface.co/OneScience-Group/Chainsaw/resolve/main/model/utils/ndo_score.py
- Command line
-
hf download hf://OneScience-Group/Chainsaw/model/utils/ndo_score.py
-
curl -L -o ndo_score.py https://huggingface.co/OneScience-Group/Chainsaw/resolve/main/model/utils/ndo_score.py
4.07 kB
| from collections import defaultdict | |
| import copy | |
| import numpy as np | |
| def check_unique_assignments(assn): | |
| res_counts = defaultdict(int) # check for repeated residues | |
| for res_ids in assn.values(): | |
| for res_id in res_ids: | |
| res_counts[res_id] += 1 | |
| assert all([v == 1 for v in res_counts.values()]), f"Non-unique domain assignment {assn}" | |
| def make_domain_dict(domain_str, n_res): | |
| """ | |
| Converts string representation of domain boundaries to a dictionary | |
| Delimit separate domains with commas , and discontinuous domains | |
| with underscores _. Residue ranges separated by hyphens -, e.g. 1-100,101-200_300-340 | |
| :param domain_str: domain boundaries expressed zero-indexed sequential e.g. 1-100,101-200_300-340 | |
| :param n_res: number of residues in the sequence | |
| :return: | |
| """ | |
| domain_dict = {} | |
| bounds = domain_str.split(',') | |
| assigned_res = set() | |
| for i, bound in enumerate(bounds): | |
| if len(bound): | |
| for segment in bound.split('_'): | |
| if '-' in segment: | |
| start, end = segment.split('-') | |
| else: | |
| start = end = segment | |
| segment_res = set(range(int(start), int(end) + 1)) | |
| assert len(segment_res.intersection(assigned_res)) == 0, f"Overlapping domain assignments {domain_str}" | |
| assigned_res.update(segment_res) | |
| domain_dict[f"D{i + 1}"] = domain_dict.get(f"D{i + 1}", []) + list(segment_res) | |
| domain_dict['linker'] = list(set(range(n_res)).difference(assigned_res)) | |
| return domain_dict | |
| def ndo_score(true: dict, pred: dict): | |
| """ | |
| Normalized Domain Overlap Score | |
| Approximately corresponds to the fraction of residues that are assigned to the correct domain | |
| (Tai, C.H., et al. Evaluation of domain prediction in CASP6. Proteins 2005;61:183-192.) | |
| Full description of algorithm at https://ccrod.cancer.gov/confluence/display/CCRLEE/NDO] | |
| https://ccrod.cancer.gov/confluence/display/CCRLEE/NDO | |
| :param true: dict of domain assignments, with keys "linker" and "dX" where X is the domain number | |
| :param pred: dict of domain assignments, with keys "linker" and "dX" where X is the domain number | |
| example domain dictionary: {'linker':[0,1], 'D1':[2,3,4,8,9], 'D2':[5,6,7]} | |
| """ | |
| # domains definitions must be mutually exclusive. | |
| check_unique_assignments(true) | |
| check_unique_assignments(pred) | |
| # alternative data structure would be a list of res ids for linker, plus a list of lists of res ids for domains. | |
| true = copy.deepcopy(true) | |
| pred = copy.deepcopy(pred) | |
| n_dom_pred = len([k for k in pred.keys() if k != "linker"]) | |
| n_dom_gt = len([k for k in true.keys() if k != "linker"]) | |
| # linkers are treated specially, so put them at row/col 0 to make this easy | |
| pred_linker = pred.pop("linker", []) | |
| gt_linker = true.pop("linker", []) | |
| gt_res_ids = [gt_linker] + list(true.values()) | |
| pred_res_ids = [pred_linker] + list(pred.values()) | |
| overlap = np.zeros((n_dom_pred + 1, n_dom_gt + 1)) | |
| assert len(gt_res_ids) == overlap.shape[1] and len(pred_res_ids) == overlap.shape[0] | |
| for i, p_res in enumerate(pred_res_ids): | |
| for j, gt_res in enumerate(gt_res_ids): | |
| overlap[i, j] = len(set(p_res).intersection(set(gt_res))) | |
| # modified from v0: max overlap for a domain cannot be with a linker (hence max slices start at 1) | |
| if overlap.shape[0] == 1 or overlap.shape[1] == 1: | |
| # either the predictions or the true labels (or both) are all linker | |
| print('NDO score undefined for case where all linker') | |
| return 0 | |
| row_scores = overlap[1:, 1:].max(axis=1) | |
| row_scores -= (overlap[1:].sum(axis=1) - row_scores) | |
| col_scores = overlap[1:, 1:].max(axis=0) | |
| col_scores -= (overlap[:, 1:].sum(axis=0) - col_scores) | |
| total_score = (row_scores.sum() + col_scores.sum()) / 2 | |
| # count number of domain (non-linker) residues to normalize | |
| max_score = sum([len(gt_res) for gt_res in gt_res_ids[1:]]) | |
| return total_score / max_score | |