| import io |
| import random |
| import warnings |
| import torch |
| import webdataset as wds |
|
|
| from pathlib import Path |
| from torch.utils.data import Dataset |
|
|
| from src.data.data_utils import TensorDict, collate_entity |
| from src.constants import WEBDATASET_SHARD_SIZE, WEBDATASET_VAL_SIZE |
|
|
|
|
| class ProcessedLigandPocketDataset(Dataset): |
| def __init__(self, pt_path, ligand_transform=None, pocket_transform=None, |
| catch_errors=False): |
|
|
| self.ligand_transform = ligand_transform |
| self.pocket_transform = pocket_transform |
| self.catch_errors = catch_errors |
| self.pt_path = pt_path |
|
|
| self.data = torch.load(pt_path) |
|
|
| |
| for entity in ['ligands', 'pockets']: |
| self.data[entity]['size'] = torch.tensor([len(x) for x in self.data[entity]['x']]) |
| self.data[entity]['n_bonds'] = torch.tensor([len(x) for x in self.data[entity]['bond_one_hot']]) |
|
|
| def __len__(self): |
| return len(self.data['ligands']['name']) |
|
|
| def __getitem__(self, idx): |
| data = {} |
| data['ligand'] = {key: val[idx] for key, val in self.data['ligands'].items()} |
| data['pocket'] = {key: val[idx] for key, val in self.data['pockets'].items()} |
| try: |
| if self.ligand_transform is not None: |
| data['ligand'] = self.ligand_transform(data['ligand']) |
| if self.pocket_transform is not None: |
| data['pocket'] = self.pocket_transform(data['pocket']) |
| except (RuntimeError, ValueError) as e: |
| if self.catch_errors: |
| warnings.warn(f"{type(e).__name__}('{e}') in data transform. " |
| f"Returning random item instead") |
| |
| rand_idx = random.randint(0, len(self) - 1) |
| return self[rand_idx] |
| else: |
| raise e |
| return data |
|
|
| @staticmethod |
| def collate_fn(batch_pairs, ligand_transform=None): |
|
|
| out = {} |
| for entity in ['ligand', 'pocket']: |
| batch = [x[entity] for x in batch_pairs] |
|
|
| if entity == 'ligand' and ligand_transform is not None: |
| max_size = max(x['size'].item() for x in batch) |
| |
| batch = [ligand_transform(x, max_size=max_size) for x in batch] |
|
|
| out[entity] = TensorDict(**collate_entity(batch)) |
|
|
| return out |
|
|
|
|
| class ClusteredDataset(ProcessedLigandPocketDataset): |
| def __init__(self, pt_path, ligand_transform=None, pocket_transform=None, |
| catch_errors=False): |
| super().__init__(pt_path, ligand_transform, pocket_transform, catch_errors) |
| self.clusters = list(self.data['clusters'].values()) |
|
|
| def __len__(self): |
| return len(self.clusters) |
|
|
| def __getitem__(self, cidx): |
| cluster_inds = self.clusters[cidx] |
| |
| idx = random.choice(cluster_inds) |
| return super().__getitem__(idx) |
|
|
| class DPODataset(ProcessedLigandPocketDataset): |
| def __init__(self, pt_path, ligand_transform=None, pocket_transform=None, |
| catch_errors=False): |
| self.ligand_transform = ligand_transform |
| self.pocket_transform = pocket_transform |
| self.catch_errors = catch_errors |
| self.pt_path = pt_path |
|
|
| self.data = torch.load(pt_path) |
|
|
| if not 'pockets' in self.data: |
| self.data['pockets'] = self.data['pockets_w'] |
| if not 'ligands' in self.data: |
| self.data['ligands'] = self.data['ligands_w'] |
|
|
| if ( |
| len(self.data["ligands"]["name"]) |
| != len(self.data["ligands_l"]["name"]) |
| != len(self.data["pockets"]["name"]) |
| ): |
| raise ValueError( |
| "Error while importing DPO Dataset: Number of ligands winning, ligands losing and pockets must be the same" |
| ) |
|
|
| |
| for entity in ['ligands', 'ligands_l', 'pockets']: |
| self.data[entity]['size'] = torch.tensor([len(x) for x in self.data[entity]['x']]) |
| self.data[entity]['n_bonds'] = torch.tensor([len(x) for x in self.data[entity]['bond_one_hot']]) |
|
|
| def __len__(self): |
| return len(self.data["ligands"]["name"]) |
|
|
| def __getitem__(self, idx): |
| data = {} |
| data['ligand'] = {key: val[idx] for key, val in self.data['ligands'].items()} |
| data['ligand_l'] = {key: val[idx] for key, val in self.data['ligands_l'].items()} |
| data['pocket'] = {key: val[idx] for key, val in self.data['pockets'].items()} |
| try: |
| if self.ligand_transform is not None: |
| data['ligand'] = self.ligand_transform(data['ligand']) |
| data['ligand_l'] = self.ligand_transform(data['ligand_l']) |
| if self.pocket_transform is not None: |
| data['pocket'] = self.pocket_transform(data['pocket']) |
| except (RuntimeError, ValueError) as e: |
| if self.catch_errors: |
| warnings.warn(f"{type(e).__name__}('{e}') in data transform. " |
| f"Returning random item instead") |
| |
| rand_idx = random.randint(0, len(self) - 1) |
| return self[rand_idx] |
| else: |
| raise e |
| return data |
| |
| @staticmethod |
| def collate_fn(batch_pairs, ligand_transform=None): |
|
|
| out = {} |
| for entity in ['ligand', 'ligand_l', 'pocket']: |
| batch = [x[entity] for x in batch_pairs] |
|
|
| if entity in ['ligand', 'ligand_l'] and ligand_transform is not None: |
| max_size = max(x['size'].item() for x in batch) |
| batch = [ligand_transform(x, max_size=max_size) for x in batch] |
|
|
| out[entity] = TensorDict(**collate_entity(batch)) |
|
|
| return out |
|
|
| |
| |
| |
|
|
| class ProteinLigandWebDataset(wds.WebDataset): |
| @staticmethod |
| def collate_fn(batch_pairs, ligand_transform=None): |
| return ProcessedLigandPocketDataset.collate_fn(batch_pairs, ligand_transform) |
|
|
|
|
| def wds_decoder(key, value): |
| return torch.load(io.BytesIO(value)) |
|
|
|
|
| def preprocess_wds_item(data): |
| out = {} |
| for entity in ['ligand', 'pocket']: |
| out[entity] = data['pt'][entity] |
| for attr in ['size', 'n_bonds']: |
| if torch.is_tensor(out[entity][attr]): |
| assert len(out[entity][attr]) == 0 |
| out[entity][attr] = 0 |
|
|
| return out |
|
|
|
|
| def get_wds(data_path, stage, ligand_transform=None, pocket_transform=None): |
| current_data_dir = Path(data_path, stage) |
| shards = sorted(current_data_dir.glob('shard-?????.tar'), key=lambda s: int(s.name.split('-')[-1].split('.')[0])) |
| min_shard = min(shards).name.split('-')[-1].split('.')[0] |
| max_shard = max(shards).name.split('-')[-1].split('.')[0] |
| total_size = (int(max_shard) - int(min_shard) + 1) * WEBDATASET_SHARD_SIZE if stage == 'train' else WEBDATASET_VAL_SIZE |
|
|
| url = f'{data_path}/{stage}/shard-{{{min_shard}..{max_shard}}}.tar' |
| ligand_transform_wrapper = lambda _data: _data |
| pocket_transform_wrapper = lambda _data: _data |
|
|
| if ligand_transform is not None: |
| def ligand_transform_wrapper(_data): |
| _data['pt']['ligand'] = ligand_transform(_data['pt']['ligand']) |
| return _data |
| |
| if pocket_transform is not None: |
| def pocket_transform_wrapper(_data): |
| _data['pt']['pocket'] = pocket_transform(_data['pt']['pocket']) |
| return _data |
|
|
| return ( |
| ProteinLigandWebDataset(url, nodesplitter=wds.split_by_node) |
| .decode(wds_decoder) |
| .map(ligand_transform_wrapper) |
| .map(pocket_transform_wrapper) |
| .map(preprocess_wds_item) |
| .with_length(total_size) |
| ) |
|
|