Download model/pxdesign/data/json_parser.py from OneScience-Group/PXDesign: direct link, hf CLI and curl.
- Browser
- Download file 9.81 kB
-
https://huggingface.co/OneScience-Group/PXDesign/resolve/main/model/pxdesign/data/json_parser.py
- Command line
-
hf download hf://OneScience-Group/PXDesign/model/pxdesign/data/json_parser.py
-
curl -L -o json_parser.py https://huggingface.co/OneScience-Group/PXDesign/resolve/main/model/pxdesign/data/json_parser.py
9.81 kB
| # Copyright 2025 ByteDance and/or its affiliates. | |
| # | |
| # Licensed under the Apache License, Version 2.0 (the "License"); | |
| # you may not use this file except in compliance with the License. | |
| # You may obtain a copy of the License at | |
| # | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, software | |
| # distributed under the License is distributed on an "AS IS" BASIS, | |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| # See the License for the specific language governing permissions and | |
| # limitations under the License. | |
| import copy | |
| import logging | |
| import numpy as np | |
| from protenix.data.json_parser import ( | |
| DNA_1to3, | |
| PROTEIN_1to3, | |
| RNA_1to3, | |
| _build_polymer_atom_array, | |
| add_reference_features, | |
| build_ligand, | |
| lig_file_to_atom_info, | |
| rdkit_mol_to_atom_info, | |
| ) | |
| from protenix.data.parser import MMCIFParser | |
| from protenix.utils.file_io import load_gzip_pickle | |
| logger = logging.getLogger(__name__) | |
| def remove_unresolved_residue_in_atom_array(atom_array): | |
| coord_mask = atom_array.is_resolved.astype(bool) | |
| res_ids = atom_array.res_id | |
| chain_ids = atom_array.chain_id | |
| res_chain_ids_to_mask = set(zip(res_ids[coord_mask], chain_ids[coord_mask])) | |
| new_mask = np.array( | |
| [ | |
| not (res_id, chain_id) in res_chain_ids_to_mask | |
| for res_id, chain_id in zip(res_ids, chain_ids) | |
| ] | |
| ) | |
| atom_array = atom_array[~new_mask] | |
| return atom_array | |
| def build_polymer_from_sequence(entity_info: dict): | |
| """ | |
| build a polymer from a polymer info dict | |
| example: { | |
| "name": "polymer", | |
| "sequence": "GPDSMEEVVVPEEPPKLVSALATYVQQERLCTMFLSIANKLLPLKP", | |
| "count": 1 | |
| } | |
| Args: | |
| item (dict): polymer info dict | |
| Returns: | |
| dict: {"atom_array": biotite_AtomArray_object} | |
| """ | |
| poly_type, info = list(entity_info.items())[0] | |
| if poly_type == "proteinChain": | |
| ccd_seqs = [PROTEIN_1to3[x] for x in info["sequence"]] | |
| if modifications := info.get("modifications"): | |
| for m in modifications: | |
| index = m["ptmPosition"] - 1 | |
| mtype = m["ptmType"] | |
| if mtype.startswith("CCD_"): | |
| ccd_seqs[index] = mtype[4:] | |
| else: | |
| raise ValueError(f"unknown modification type: {mtype}") | |
| if glycans := info.get("glycans"): | |
| logging.warning(f"glycans not supported: {glycans}") | |
| chain_array = _build_polymer_atom_array(ccd_seqs) | |
| elif poly_type in ("dnaSequence", "rnaSequence"): | |
| map_1to3 = DNA_1to3 if poly_type == "dnaSequence" else RNA_1to3 | |
| ccd_seqs = [map_1to3[x] for x in info["sequence"]] | |
| if modifications := info.get("modifications"): | |
| for m in modifications: | |
| index = m["basePosition"] - 1 | |
| mtype = m["modificationType"] | |
| if mtype.startswith("CCD_"): | |
| ccd_seqs[index] = mtype[4:] | |
| else: | |
| raise ValueError(f"unknown modification type: {mtype}") | |
| chain_array = _build_polymer_atom_array(ccd_seqs) | |
| else: | |
| raise ValueError( | |
| "polymer type must be proteinChain, dnaSequence or rnaSequence" | |
| ) | |
| chain_array = add_reference_features(chain_array) | |
| return {"atom_array": chain_array} | |
| def build_polymer_from_bioassombly_dict(entity_info, remove_unresolved_residue): | |
| poly_type, info = list(entity_info.items())[0] | |
| path_file = entity_info[poly_type]["path"] | |
| if path_file.endswith(".pkl.gz"): | |
| bioassembly_dict = load_gzip_pickle(path_file) | |
| else: | |
| raise ValueError(f"Unsupported structure file {path_file}!") | |
| mask_chain_id = entity_info[poly_type]["json_chain_id"] | |
| atom_array = bioassembly_dict["atom_array"] | |
| if remove_unresolved_residue: | |
| atom_array = remove_unresolved_residue_in_atom_array(atom_array) | |
| chain_array_mask = atom_array.chain_id == mask_chain_id | |
| chain_array = atom_array[chain_array_mask] | |
| if "hotspot" in entity_info[poly_type]: | |
| hotspot = entity_info[poly_type]["hotspot"] | |
| else: | |
| hotspot = [] | |
| hotspot = np.isin(chain_array.res_id, np.array(hotspot)) | |
| if "noise_level" in entity_info[poly_type]: | |
| noise = np.full(len(chain_array), float(entity_info[poly_type]["noise_level"])) | |
| else: | |
| noise = np.full(len(chain_array), 0.00) | |
| conditional_label = np.full(len(chain_array), 1).astype(bool) | |
| chain_array.set_annotation("noise_level", noise) | |
| chain_array.set_annotation("conditional_label", conditional_label) | |
| chain_array.set_annotation("hotspot", hotspot) | |
| chain_array.set_annotation("coord_from_cif", chain_array.coord) | |
| chain_array.set_annotation( | |
| "coord_from_cif_is_resolved", chain_array.is_resolved.astype(bool) | |
| ) | |
| chain_array = add_reference_features(chain_array) | |
| if "crop" in entity_info[poly_type] and entity_info[poly_type]["crop"] is not None: | |
| crop = entity_info[poly_type]["crop"] | |
| crop.replace(" ", "") | |
| crop = crop.split(",") | |
| save_list = [] | |
| for pid in crop: | |
| if "-" in pid: | |
| s, e = pid.split("-") | |
| length = int(e) - int(s) + 1 | |
| save_num = [i + int(s) for i in range(0, length)] | |
| save_list += save_num | |
| else: | |
| save_list.append(int(pid)) | |
| crop_mask = np.isin(chain_array.res_id, np.array(save_list)) | |
| chain_array = chain_array[crop_mask] | |
| # chain_array = chain_array[chain_array.atom_name != "OXT"] | |
| return {"atom_array": chain_array} | |
| def build_polymer(entity_info: dict, remove_unresolved_residue: bool = True): | |
| poly_type, info = list(entity_info.items())[0] | |
| if ( | |
| entity_info[poly_type]["sequence_type"] == "condition" | |
| and info.get("path", None) is not None | |
| ): | |
| return build_polymer_from_bioassombly_dict( | |
| entity_info, remove_unresolved_residue | |
| ) | |
| assert entity_info[poly_type]["sequence_type"] in ["design", "condition"] | |
| assert "sequence" in info | |
| chain_array = build_polymer_from_sequence(entity_info=entity_info)["atom_array"] | |
| # Add hotspot if exists | |
| if "hotspot" in entity_info[poly_type]: | |
| hotspot = entity_info[poly_type]["hotspot"] | |
| else: | |
| hotspot = [] | |
| hotspot = np.isin(chain_array.res_id, np.array(hotspot)) | |
| chain_array.set_annotation("hotspot", hotspot) | |
| # Add noise: currently not used | |
| noise = np.full(len(chain_array), 0.00) | |
| chain_array.set_annotation("noise_level", noise) | |
| # Add condition label | |
| if entity_info[poly_type]["sequence_type"] == "design": | |
| conditional_label = np.full(len(chain_array), 0).astype(bool) | |
| else: | |
| assert entity_info[poly_type]["sequence_type"] == "condition" | |
| conditional_label = np.full(len(chain_array), 1).astype(bool) | |
| chain_array.set_annotation("conditional_label", conditional_label.copy()) | |
| res_name = chain_array.res_name.copy() | |
| res_name[~conditional_label] = "xpb" | |
| chain_array.set_annotation("res_name", res_name) | |
| ## coord * 0 -> not from cif file | |
| chain_array.set_annotation("coord_from_cif", chain_array.coord * 0.0) | |
| chain_array.set_annotation( | |
| "coord_from_cif_is_resolved", np.full(len(chain_array), 0).astype(bool) | |
| ) | |
| if "is_resolved" not in chain_array._annot: | |
| chain_array.set_annotation( | |
| "is_resolved", np.ones((len(chain_array),)).astype(bool) | |
| ) | |
| if "crop" in entity_info[poly_type] and entity_info[poly_type]["crop"] is not None: | |
| crop = entity_info[poly_type]["crop"] | |
| crop.replace(" ", "") | |
| crop = crop.split(",") | |
| save_list = [] | |
| for pid in crop: | |
| if "-" in pid: | |
| s, e = pid.split("-") | |
| length = int(e) - int(s) + 1 | |
| save_num = [i + int(s) for i in range(0, length)] | |
| save_list += save_num | |
| else: | |
| save_list.append(int(pid)) | |
| crop_mask = np.isin(chain_array.res_id, np.array(save_list)) | |
| chain_array = chain_array[crop_mask] | |
| return {"atom_array": chain_array} | |
| def add_entity_atom_array(single_job_dict: dict) -> dict: | |
| """ | |
| add atom_array to each entity in single_job_dict | |
| args: | |
| single_job_dict (dict): input job dict | |
| returns: | |
| dict: deepcopy and updated job dict with atom_array | |
| """ | |
| single_job_dict = copy.deepcopy(single_job_dict) | |
| sequences = single_job_dict["sequences"] | |
| smiles_ligand_count = 0 | |
| for entity_info in sequences: | |
| if info := entity_info.get("proteinChain"): | |
| atom_info = build_polymer(entity_info) | |
| elif info := entity_info.get("dnaSequence"): | |
| atom_info = build_polymer(entity_info) | |
| elif info := entity_info.get("rnaSequence"): | |
| atom_info = build_polymer(entity_info) | |
| elif info := entity_info.get("condition_ligand"): | |
| atom_info = build_polymer(entity_info) | |
| elif info := entity_info.get("ligand"): | |
| atom_info = build_ligand(entity_info) | |
| if not info["ligand"].startswith("CCD_"): | |
| smiles_ligand_count += 1 | |
| assert smiles_ligand_count <= 99, "too many smiles ligands" | |
| # use lower case res_name (l01, l02, ..., l99) to avoid conflict with CCD code | |
| atom_info["atom_array"].res_name[:] = f"l{smiles_ligand_count:02d}" | |
| elif info := entity_info.get("ion"): | |
| atom_info = build_ligand(entity_info) | |
| else: | |
| raise ValueError( | |
| "entity type must be proteinChain, dnaSequence, rnaSequence, ligand or ion" | |
| ) | |
| info.update(atom_info) | |
| return single_job_dict | |