PXDesign / model /pxdesign /data /json_parser.py
anzhi2710gmailcom's picture
Upload folder using huggingface_hub (part 2)
4d20b62 verified
Raw
History Blame Contribute Delete
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