DrugFlow / src /data /dataset.py
mority's picture
Upload 53 files
6e7d4ba verified
Raw
History Blame Contribute Delete
8.06 kB
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)
# add number of nodes for convenience
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")
# replace bad item with a random one
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)
# TODO: might have to remove elements from batch if processing fails, warn user in that case
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 = cluster_inds[random.randint(0, len(cluster_inds) - 1)]
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"
)
# add number of nodes for convenience
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")
# replace bad item with a random one
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
##########################################
############### WebDatasets ##############
##########################################
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)
)