| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import torch,math |
| from pyuul.sources.globalVariables import * |
|
|
| import numpy as np |
| import random |
|
|
| def setup_seed(seed): |
| torch.manual_seed(seed) |
| torch.cuda.manual_seed_all(seed) |
| np.random.seed(seed) |
| random.seed(seed) |
| torch.backends.cudnn.deterministic = True |
| setup_seed(100) |
|
|
| class Voxels(torch.nn.Module): |
| |
| def __init__(self, device=torch.device("cpu"),sparse=True): |
| """ |
| Constructor for the Voxels class, which builds the main PyUUL object. |
| |
| Parameters |
| ---------- |
| |
| device : torch.device |
| The device on which the model should run. E.g. torch.device("cuda") or torch.device("cpu:0") |
| sparse : bool |
| Use sparse tensors calculation when possible |
| |
| Returns |
| ------- |
| """ |
| super(Voxels, self).__init__() |
|
|
| self.sparse=sparse |
| self.boxsize = None |
| self.dev = device |
|
|
| def __transform_coordinates(self,coords,radius=None): |
| """ |
| Private function that transform the coordinates to fit them in the 3d box. It also takes care of the resolution. |
| |
| Parameters |
| ---------- |
| coords : torch.Tensor |
| Coordinates of the atoms. Shape ( batch, numberOfAtoms, 3 ) |
| radius : torch.Tensor or None |
| Radius of the atoms. Shape ( batch, numberOfAtoms ) |
| |
| Returns |
| ------- |
| coords : torch.Tensor |
| transformed coordinates |
| |
| """ |
| coords = (coords*self.dilatation)- self.translation |
| if not radius is None: |
| radius = radius*self.dilatation |
| return coords,radius |
| else: |
| return coords |
| ''' |
| def get_coords_voxel(self, voxel_indices, resolution): |
| """ |
| returns the coordinates of the center of the voxel provided its indices. |
| |
| Parameters |
| ---------- |
| voxel_indices : torch.Tensor |
| Coordinates of the atoms. Shape ( batch, numberOfAtoms, 3 ) |
| resolution : torch.Tensor or None |
| Radius of the atoms. Shape ( batch, numberOfAtoms ) |
| |
| Returns |
| ------- |
| """ |
| #voxel_indices is a n,3 long tensor |
| centersCoords = voxel_indices + 0.5*resolution |
| return (centersCoords + self.translation)/self.dilatation |
| ''' |
| def __define_spatial_conformation(self,mincoords,cubes_around_atoms_dim,resolution): |
| """ |
| Private function that defines the space of the volume. Takes resolution and margins into consideration. |
| |
| Parameters |
| ---------- |
| mincoords : torch.Tensor |
| minimum coordinates of each macromolecule of the batch. Shape ( batch, 3 ) |
| cubes_around_atoms_dim : int |
| maximum distance in number of voxels to check for atom contribution to occupancy of a voxel |
| resolution : float |
| side in A of a voxel. The lower this value is the higher the resolution of the final representation will be |
| Returns |
| ------- |
| """ |
| self.translation=(mincoords-(cubes_around_atoms_dim)).unsqueeze(1) |
| self.dilatation = 1.0/resolution |
|
|
| ''' |
| def find_cubes_indices(self,coords): |
| coords_scaled = self.transform_coordinates(coords) |
| return torch.trunc(coords_scaled.data).long() |
| ''' |
|
|
| def forward( self,coords, radius,channels,numberchannels=None,resolution=1, cubes_around_atoms_dim=5, steepness=10,function="sigmoid"): |
| """ |
| Voxels representation of the macromolecules |
| |
| Parameters |
| ---------- |
| coords : torch.Tensor |
| Coordinates of the atoms. Shape ( batch, numberOfAtoms, 3 ). Can be calculated from a PDB file using utils.parsePDB |
| radius : torch.Tensor |
| Radius of the atoms. Shape ( batch, numberOfAtoms ). Can be calculated from a PDB file using utils.parsePDB and utils.atomlistToRadius |
| channels: torch.LongTensor |
| channels of the atoms. Atoms of the same type shold belong to the same channel. Shape ( batch, numberOfAtoms ). Can be calculated from a PDB file using utils.parsePDB and utils.atomlistToChannels |
| numberchannels : int or None |
| maximum number of channels. if None, max(atNameHashing) + 1 is used |
| |
| cubes_around_atoms_dim : int |
| maximum distance in number of voxels for which the contribution to occupancy is taken into consideration. Every atom that is farer than cubes_around_atoms_dim voxels from the center of a voxel does no give any contribution to the relative voxel occupancy |
| resolution : float |
| side in A of a voxel. The lower this value is the higher the resolution of the final representation will be |
| |
| steepness : float or int |
| steepness of the sigmoid occupancy function. |
| |
| function : "sigmoid" or "gaussian" |
| occupancy function to use. Can be sigmoid (every atom has a sigmoid shaped occupancy function) or gaussian (based on Li et al. 2014) |
| Returns |
| ------- |
| volume : torch.Tensor |
| voxel representation of the macromolecules in the batch. Shape ( batch, channels, x,y,z), where x,y,z are the size of the 3D volume in which the macromolecules have been represented |
| |
| """ |
| padding_mask = ~channels.eq(PADDING_INDEX) |
| if numberchannels is None: |
| numberchannels = int(channels[padding_mask].max().cpu().data+1) |
| self.featureVectorSize = numberchannels |
| self.function = function |
|
|
| arange_type = torch.int16 |
|
|
| gx = torch.arange(-cubes_around_atoms_dim, cubes_around_atoms_dim + 1, device=self.dev, dtype=arange_type) |
| gy = torch.arange(-cubes_around_atoms_dim, cubes_around_atoms_dim + 1, device=self.dev, dtype=arange_type) |
| gz = torch.arange(-cubes_around_atoms_dim, cubes_around_atoms_dim + 1, device=self.dev, dtype=arange_type) |
| self.lato = gx.shape[0] |
|
|
| x1 = gx.unsqueeze(1).expand(self.lato, self.lato).unsqueeze(-1) |
| x2 = gy.unsqueeze(0).expand(self.lato, self.lato).unsqueeze(-1) |
|
|
| xy = torch.cat([x1, x2], dim=-1).unsqueeze(2).expand(self.lato, self.lato, self.lato, 2) |
| x3 = gz.unsqueeze(0).unsqueeze(1).expand(self.lato, self.lato, self.lato).unsqueeze(-1) |
|
|
| del gx, gy, gz, x1, x2 |
|
|
| self.standard_cube = torch.cat([xy, x3], dim=-1).unsqueeze(0).unsqueeze(0) |
|
|
|
|
|
|
| |
| |
| |
| |
|
|
|
|
|
|
| mincoords = torch.min(coords[:, :, :], dim=1)[0] |
| mincoords = torch.trunc(mincoords / resolution) |
|
|
| |
| box_size_x = (math.ceil(torch.max(coords[padding_mask][:,0])/resolution)-mincoords[:,0].min())+(2*cubes_around_atoms_dim+1) |
| box_size_y = (math.ceil(torch.max(coords[padding_mask][:,1])/resolution)-mincoords[:,1].min())+(2*cubes_around_atoms_dim+1) |
| box_size_z = (math.ceil(torch.max(coords[padding_mask][:,2])/resolution)-mincoords[:,2].min())+(2*cubes_around_atoms_dim+1) |
| |
|
|
| self.__define_spatial_conformation(mincoords,cubes_around_atoms_dim,resolution) |
| coords,radius = self.__transform_coordinates(coords,radius) |
|
|
| boxsize = (int(box_size_x),int(box_size_y),int(box_size_z)) |
| self.boxsize=boxsize |
|
|
| |
| if max(boxsize)<256: |
| self.dtype_indices=torch.uint8 |
| else: |
| self.dtype_indices = torch.int16 |
|
|
| if self.function=="sigmoid": |
| volume = self.__forward_actual_calculation(coords, boxsize, radius, channels,padding_mask,steepness,resolution) |
| elif self.function=="gaussian": |
| volume = self.__forward_actual_calculationGaussian(coords, boxsize, radius, channels, padding_mask,resolution) |
| return volume |
|
|
| def __forward_actual_calculationGaussian(self, coords_scaled, boxsize, radius, atNameHashing, padding_mask,resolution): |
| """ |
| private function for the calculation of the gaussian voxel occupancy |
| |
| Parameters |
| ---------- |
| coords_scaled : torch.LongTensor |
| Discrete Coordinates of the atoms. Shape ( batch, numberOfAtoms, 3 ) |
| boxsize : torch.LongTensor |
| The size of the box in which the macromolecules are represented |
| radius : torch.Tensor |
| Radius of the atoms. Shape ( batch, numberOfAtoms ). Can be calculated from a PDB file using utils.parsePDB and utils.atomlistToRadius |
| atNameHashing: torch.LongTensor |
| channels of the atoms. Atoms of the same type shold belong to the same channel. Shape ( batch, numberOfAtoms ). Can be calculated from a PDB file using utils.parsePDB and utils.atomlistToChannels |
| resolution : float |
| side in A of a voxel. The lower this value is the higher the resolution of the final representation will be |
| padding_mask : torch.BoolTensor |
| tensor to mask the padding. Shape (batch, numberOfAtoms) |
| Returns |
| ------- |
| volume : torch.Tensor |
| voxel representation of the macromolecules in the batch with Gaussian occupancy function. Shape ( batch, channels, x,y,z), where x,y,z are the size of the 3D volume in which the macromolecules have been represented |
| |
| """ |
| batch = coords_scaled.shape[0] |
| dev = self.dev |
| L = coords_scaled.shape[1] |
|
|
| discrete_coordinates = torch.trunc(coords_scaled.data).to(self.dtype_indices) |
|
|
| |
|
|
| |
| radius = radius.unsqueeze(2).unsqueeze(3).unsqueeze(4) |
| atNameHashing = atNameHashing.unsqueeze(2).unsqueeze(3).unsqueeze(4) |
| coords_scaled = coords_scaled.unsqueeze(2).unsqueeze(3).unsqueeze(4) |
| discrete_coordinates = discrete_coordinates.unsqueeze(2).unsqueeze(3).unsqueeze(4) |
| distmat_standard_cube = torch.norm( |
| coords_scaled - ((discrete_coordinates + self.standard_cube + 1) + 0.5 * resolution), dim=-1).to( |
| coords_scaled.dtype) |
|
|
| atNameHashing = atNameHashing.long() |
| |
| ''' |
| exponent = self.steepness*(distmat_standard_cube-radius) |
| |
| exp_mask = exponent.ge(10) |
| exponent = torch.masked_fill(exponent,exp_mask, 10) |
| |
| volume_cubes = 1.0/(1.0+torch.exp(exponent)) |
| ''' |
| |
| sigma = 0.93 |
| exponent = -distmat_standard_cube[padding_mask] ** 2 / (sigma ** 2 * radius[padding_mask] ** 2) |
| exp_mask = exponent.ge(10) |
| exponent = torch.masked_fill(exponent, exp_mask, 10) |
| volume_cubes = torch.exp(exponent) |
|
|
| |
| batch_list = torch.arange(batch,device=dev).unsqueeze(1).unsqueeze(1).unsqueeze(1).unsqueeze(1).expand(batch,L,self.lato,self.lato,self.lato) |
|
|
| cubes_coords = (discrete_coordinates[padding_mask] + self.standard_cube.squeeze(0) + 1)[~exp_mask] |
| atNameHashing = atNameHashing[padding_mask].expand(-1,self.lato,self.lato,self.lato) |
| if self.sparse: |
|
|
| index_tens = torch.cat( |
| [batch_list[padding_mask][~exp_mask].view(-1).unsqueeze(0), |
| atNameHashing[~exp_mask].unsqueeze(0), |
| cubes_coords[:,0].unsqueeze(0), |
| cubes_coords[:,1].unsqueeze(0), |
| cubes_coords[:,2].unsqueeze(0), |
| ]) |
| |
|
|
| volume_cubes = volume_cubes[~exp_mask].view(-1) |
| volume_cubes = torch.log(1 - volume_cubes.contiguous()) |
| |
| volume = torch.sparse_coo_tensor(indices=index_tens, values=volume_cubes.exp(), size=[batch, self.featureVectorSize, boxsize[0] , boxsize[1] , boxsize[2] ]).coalesce() |
| volume = torch.sparse_coo_tensor(volume.indices(),1 - volume.values(), volume.shape) |
|
|
| else: |
| volume = torch.zeros(batch,boxsize[0]+1,boxsize[1]+1,boxsize[2]+1,self.featureVectorSize,device=dev,dtype=torch.float) |
| |
| index = (batch_list[padding_mask][~exp_mask].view(-1).long(), |
| cubes_coords[:,0].long(), |
| cubes_coords[:,1].long(), |
| cubes_coords[:,2].long(), |
| atNameHashing[~exp_mask]) |
| volume_cubes=volume_cubes[~exp_mask].view(-1) |
|
|
| volume_cubes = torch.log(1 - volume_cubes.contiguous()) |
| volume = 1- torch.exp(volume.index_put(index,volume_cubes,accumulate=True)) |
| |
| volume=volume.permute(0,4,1,2,3) |
| |
|
|
| return volume |
|
|
|
|
|
|
| return volume |
|
|
| def __sparseClamp(self,volume, minv, maxv): |
| vals = volume.values() |
| ind = volume.indices() |
|
|
| vals = vals.clamp(minv, maxv) |
| volume = torch.sparse_coo_tensor(indices=ind, values=vals, size=volume.shape).coalesce() |
| return volume |
|
|
| def __forward_actual_calculation(self, coords_scaled, boxsize, radius,atNameHashing,padding_mask,steepness,resolution): |
| """ |
| private function for the calculation of the gaussian voxel occupancy |
| |
| Parameters |
| ---------- |
| coords_scaled : torch.LongTensor |
| Discrete Coordinates of the atoms. Shape ( batch, numberOfAtoms, 3 ) |
| boxsize : torch.LongTensor |
| The size of the box in which the macromolecules are represented |
| radius : torch.Tensor |
| Radius of the atoms. Shape ( batch, numberOfAtoms ). Can be calculated from a PDB file using utils.parsePDB and utils.atomlistToRadius |
| atNameHashing: torch.LongTensor |
| channels of the atoms. Atoms of the same type shold belong to the same channel. Shape ( batch, numberOfAtoms ). Can be calculated from a PDB file using utils.parsePDB and utils.atomlistToChannels |
| resolution : float |
| side in A of a voxel. The lower this value is the higher the resolution of the final representation will be |
| padding_mask : torch.BoolTensor |
| tensor to mask the padding. Shape (batch, numberOfAtoms) |
| steepness : float |
| steepness of the sigmoid function (coefficient of the exponent) |
| |
| Returns |
| ------- |
| volume : torch.Tensor |
| voxel representation of the macromolecules in the batch with Sigmoid occupancy function. Shape ( batch, channels, x,y,z), where x,y,z are the size of the 3D volume in which the macromolecules have been represented |
| |
| """ |
| batch = coords_scaled.shape[0] |
| dev=self.dev |
| L = coords_scaled.shape[1] |
| |
| discrete_coordinates = torch.trunc(coords_scaled.data).to(self.dtype_indices) |
|
|
| |
|
|
| |
| radius = radius.unsqueeze(2).unsqueeze(3).unsqueeze(4) |
| atNameHashing = atNameHashing.unsqueeze(2).unsqueeze(3).unsqueeze(4) |
| coords_scaled = coords_scaled.unsqueeze(2).unsqueeze(3).unsqueeze(4) |
| discrete_coordinates = discrete_coordinates.unsqueeze(2).unsqueeze(3).unsqueeze(4) |
| distmat_standard_cube = torch.norm(coords_scaled-((discrete_coordinates + self.standard_cube + 1) + 0.5 * resolution), dim=-1).to(coords_scaled.dtype) |
|
|
| atNameHashing = atNameHashing.long() |
|
|
| exponent = steepness*(distmat_standard_cube[padding_mask]-radius[padding_mask]) |
| del distmat_standard_cube |
| exp_mask = exponent.ge(10) |
| exponent = torch.masked_fill(exponent,exp_mask, 10) |
|
|
| volume_cubes = 1.0/(1.0+torch.exp(exponent)) |
| |
| |
| batch_list = torch.arange(batch,device=dev).unsqueeze(1).unsqueeze(1).unsqueeze(1).unsqueeze(1).expand(batch,L,self.lato,self.lato,self.lato) |
|
|
| |
| cubes_coords = (discrete_coordinates[padding_mask] + self.standard_cube.squeeze(0) + 1)[~exp_mask] |
| atNameHashing = atNameHashing[padding_mask].expand(-1,self.lato,self.lato,self.lato) |
| if self.sparse: |
|
|
| index_tens = torch.cat( |
| [batch_list[padding_mask][~exp_mask].view(-1).unsqueeze(0), |
| atNameHashing[~exp_mask].unsqueeze(0), |
| cubes_coords[:,0].unsqueeze(0), |
| cubes_coords[:,1].unsqueeze(0), |
| cubes_coords[:,2].unsqueeze(0), |
| ]) |
| |
| volume = torch.sparse_coo_tensor(indices=index_tens, values=volume_cubes[~exp_mask].view(-1), size=[batch, self.featureVectorSize, boxsize[0] , boxsize[1] , boxsize[2] ]).coalesce() |
| volume = self.__sparseClamp(volume,0,1) |
|
|
| else: |
| volume = torch.zeros(batch,boxsize[0]+1,boxsize[1]+1,boxsize[2]+1,self.featureVectorSize,device=dev,dtype=torch.float) |
| |
| index = (batch_list[padding_mask][~exp_mask].view(-1).long(), |
| cubes_coords[:,0].long(), |
| cubes_coords[:,1].long(), |
| cubes_coords[:,2].long(), |
| atNameHashing[~exp_mask]) |
| volume_cubes=volume_cubes[~exp_mask].view(-1) |
|
|
| volume = volume.index_put(index,volume_cubes.view(-1),accumulate=True) |
|
|
| volume = -torch.nn.functional.threshold(-volume,-1,-1) |
| volume = volume.permute(0,4,1,2,3) |
|
|
| return volume |
| ''' |
| mesh will be added as soon as pytorch3d becomes a little more stable |
| def mesh(self,coords, radius,threshSurface = 0.01): |
| |
| atNameHashing= torch.zeros(radius.shape).to(self.dev) |
| mask = radius.eq(PADDING_INDEX) |
| atNameHashing = atNameHashing.masked_fill_(mask,PADDING_INDEX) |
| vol = self(coords,radius,atNameHashing).to_dense() |
| mesh = cubifyNOALIGN(vol.sum(-1),thresh=threshSurface)# creates pytorch 3d mesh from cubes. It uses a MODIFIED version of pytorch3d with no align |
| return mesh |
| ''' |
|
|
| class PointCloudSurface(torch.nn.Module): |
| def __init__(self,device="cpu"): |
| """ |
| Constructor for the CloudPointSurface class, which builds the main PyUUL object for cloud surface. |
| |
| Parameters |
| ---------- |
| device : torch.device |
| The device on which the model should run. E.g. torch.device("cuda") or torch.device("cpu:0") |
| |
| |
| Returns |
| ------- |
| """ |
| super(PointCloudSurface, self).__init__() |
|
|
| self.device=device |
|
|
| def __buildStandardSphere(self,npoints=50): |
|
|
| goldenRatio = (1 + 5 ** 0.5) / 2 |
| i = torch.arange(0, npoints,device=self.device) |
| theta = 2 * math.pi * i / goldenRatio |
| phi = torch.acos(1 - 2 * (i + 0.5) / npoints) |
|
|
| x, y, z = torch.cos(theta) * torch.sin(phi), torch.sin(theta) * torch.sin(phi), torch.cos(phi) |
|
|
| coords=torch.cat([x.unsqueeze(-1),y.unsqueeze(-1),z.unsqueeze(-1)],dim=-1) |
| |
|
|
| return coords |
|
|
| def forward(self, coords, radius, maxpoints=500,external_radius_factor=1.4): |
| """ |
| Function to calculate the surface cloud point representation of macromolecules |
| |
| Parameters |
| ---------- |
| coords : torch.Tensor |
| Coordinates of the atoms. Shape ( batch, numberOfAtoms, 3 ). Can be calculated from a PDB file using utils.parsePDB |
| radius : torch.Tensor |
| Radius of the atoms. Shape ( batch, numberOfAtoms ). Can be calculated from a PDB file using utils.parsePDB and utils.atomlistToRadius |
| maxpoints : int |
| number of points per macromolecule in the batch |
| external_radius_factor=1.4 |
| multiplicative factor of the radius in order ot define the place to sample the points around each atom. The higher this value is, the smoother the surface will be |
| Returns |
| ------- |
| surfacePointCloud : torch.Tensor |
| surface point cloud representation of the macromolecules in the batch. Shape ( batch, channels, numberOfAtoms, 3) |
| |
| """ |
| padding_mask = ~radius.eq(PADDING_INDEX) |
|
|
| batch = coords.shape[0] |
| npoints = torch.div(maxpoints,(padding_mask.sum(-1).min() + 1), rounding_mode="floor") * 2 |
|
|
| sphere = self.__buildStandardSphere(npoints) |
| finalPoints=[] |
|
|
| for b in range(batch): |
|
|
| distmat = torch.cdist(coords[b][padding_mask[b]].unsqueeze(0), coords[b][padding_mask[b]].unsqueeze(0)) |
| L=distmat.shape[1] |
| AtomSelfContributionMask = torch.eye(L, dtype=torch.bool, device=self.device).unsqueeze(0) |
| triangular_mask = ~torch.tril(torch.ones((L, L), dtype=torch.bool, device=self.device), diagonal=-1).unsqueeze(0) |
|
|
| |
| external_radius = radius * external_radius_factor |
| todoMask = (distmat[0].le(5) & (~AtomSelfContributionMask)).squeeze(0) |
| points = coords[b][padding_mask[b]].unsqueeze(0).unsqueeze(-2) - sphere.unsqueeze(0).unsqueeze(1) * external_radius[b][padding_mask[b]].unsqueeze(0).unsqueeze(-1).unsqueeze(-1) |
|
|
| p = points.expand( L, L, npoints, 3)[todoMask] |
| c = coords[b][padding_mask[b]].unsqueeze(1).unsqueeze(-2).expand( L, L, points.shape[2], 3)[todoMask] |
| r = radius[b][padding_mask[b]].unsqueeze(1).unsqueeze(-2).expand( L, L, points.shape[2])[todoMask] |
| occupancy = self.__occupancy(p, c, r) |
|
|
| point_index = torch.arange(0,L*npoints,device=self.device).view(L,npoints).unsqueeze(0).expand(L,L,npoints)[todoMask] |
| point_occupancy =torch.zeros((L*npoints),dtype=torch.float,device=self.device) |
| point_occupancy = point_occupancy.index_put_([point_index.view(-1)], occupancy.view(-1), accumulate=True) |
| point_occupancy = (1- torch.exp(point_occupancy)) |
|
|
| points_on_surfaceMask = point_occupancy.le(0.5) |
|
|
| points=points.permute(0,3,1,2).view(3,-1).transpose(0,1)[points_on_surfaceMask] |
| random_indices = torch.randint(0, points.shape[0], [maxpoints], device=self.device) |
| sampled_points = points[random_indices,:] |
|
|
| finalPoints +=[sampled_points] |
|
|
| return torch.cat(finalPoints,dim=0) |
|
|
| def __occupancy(self, points, coords, radius): |
|
|
| dist = torch.norm(points-coords,dim=-1) |
|
|
|
|
| sigma=0.93 |
| exponent = -dist**2/(sigma**2 * radius**2) |
| exp_mask = exponent.ge(10) |
| exponent = torch.masked_fill(exponent, exp_mask, 10) |
|
|
| occupancy_on_points = torch.exp(exponent) |
| return torch.log(1-occupancy_on_points) |
| return occupancy_on_points |
| del exponent |
|
|
| AtomSelfContributionMask = torch.eye(L,dtype=torch.bool,device=self.device).unsqueeze(0).expand(batch,L,L) |
| occupancy_on_points[AtomSelfContributionMask]=0.0 |
|
|
| occupancy = (1-torch.exp(torch.log(1-occupancy_on_points).sum(2))) |
| |
| |
| return occupancy |
|
|
| class PointCloudVolume(torch.nn.Module): |
| def __init__(self, device="cpu"): |
| """ |
| Constructor for the CloudPointSurface class, which builds the main PyUUL object for volumetric point cloud. |
| |
| Parameters |
| ---------- |
| device : torch.device |
| The device on which the model should run. E.g. torch.device("cuda") or torch.device("cpu:0") |
| |
| |
| Returns |
| ------- |
| """ |
| super(PointCloudVolume, self).__init__() |
|
|
| self.device = device |
|
|
| def forward(self, coords, radius, maxpoints=500): |
|
|
| """ |
| Function to calculate the volumetric cloud point representation of macromolecules |
| |
| Parameters |
| ---------- |
| coords : torch.Tensor |
| Coordinates of the atoms. Shape ( batch, numberOfAtoms, 3 ). Can be calculated from a PDB file using utils.parsePDB |
| radius : torch.Tensor |
| Radius of the atoms. Shape ( batch, numberOfAtoms ). Can be calculated from a PDB file using utils.parsePDB and utils.atomlistToRadius |
| maxpoints : int |
| number of points per macromolecule in the batch |
| |
| Returns |
| ------- |
| PointCloudVolume : torch.Tensor |
| volume point cloud representation of the macromolecules in the batch. Shape ( batch, channels, numberOfAtoms, 3) |
| |
| """ |
|
|
| padding_mask = ~radius.eq(PADDING_INDEX) |
|
|
| |
|
|
| batch = coords.shape[0] |
| L = coords.shape[1] |
|
|
| batched = [] |
| for i in range(batch): |
| mean = coords[i][padding_mask[i]] |
|
|
| sampled = radius[i][padding_mask[i]].sqrt().unsqueeze(-1) * torch.randn((mean.size()), device=self.device) + mean |
| p = sampled.view(-1,3) |
| random_indices = torch.randint(0, p.shape[0], [maxpoints], device=self.device) |
| batched+=[p[random_indices].unsqueeze(0)] |
|
|
| batched = torch.cat(batched,dim=0) |
| return batched |
|
|
|
|